When ICanUp needed short updates about the site, product, releases, and changes, the simplest design was obvious: add another type to Post.
News and Post genuinely share many capabilities. Both have a slug, translations, publication state, published_at, cover media, full content, SEO, and a public page.
But instead of asking how many columns are identical?, I found another question more useful: do News and Post represent the same thing to the application?

At Field Level, Post and News Really Do Look Similar
If I look only at persistence, combining them appears reasonable.
Capability | Post | News |
|---|---|---|
Slug | Yes | Yes |
UK / EN | Yes | Yes |
Publication state | Yes | Yes |
| Yes | Yes |
Cover through Media | Yes | Yes |
ContentBlocks | Yes | Yes |
SEO | Yes | Yes |
If repeated columns were the only criterion, News would almost certainly have become another Post variant.
The Simplest Design Was a type Column in posts
Conceptually, that model could be very small:
posts idslugtypestatuspublished_atimage_media_id...For example:
type = posttype = newsFor the first version, this could even be faster.
But every future difference would gradually turn the shared model into a collection of conditional branches.
The Real Difference Is Behavior, Not the Table
A Post in my application already has a specific role: it is a technical publication with categories, optional series membership, and its own editorial workflow.
News exists for another purpose: record a site event, release, product change, or update, while still allowing a full ContentBlocks article when needed.
Post | News |
|---|---|
Technical publication | News item or announcement |
Category | Bounded News type |
Can belong to Post Series | Does not need Post Series |
Long-form content is primary | May be short or have full content |
Own Posts admin list | Own News admin list |
The technical capabilities overlap, but the editorial meaning is already different.
I Separated the Domain Without Duplicating Infrastructure
This became the key architectural decision for me.
A separate domain does not mean writing localization, media handling, block editing, or SEO twice.
Post domain --------------------+ |News domain --------------------+--> Localization +--> Media +--> ContentBlocks +--> SEO +--> Publication rules +--> AnalyticsI reuse platform capabilities without forcing different domain concepts into one model just to reuse code.
News Received Its Own Persistence
News therefore has its own news and news_translations tables.
Its state does not need to be reconstructed from Posts by constantly applying type = news.
postspost_translations newsnews_translationsThat does not mean I created another localization system. News follows the same localization approach already used across ICanUp.
A News type Is Not the Same as Post Categories
In the first version, I deliberately did not add a separate category tree for News.
A small bounded set of types is enough:
packageproductsitereleaseupdateThis is another difference from Post.
Posts have full category organization. News needs a smaller classification describing the nature of the update itself.
If News Were a Post, Conditionals Would Keep Growing
A shared model saves a few classes and tables at first.
As behavior diverges, checks begin to appear:
if ($post->type === 'news') { // News-specific behavior.} if ($post->type !== 'news') { // Post-only behavior.}Then those checks spread into validation, queries, policies, admin pages, analytics, SEO, category handling, and public presentation.
At some point, the shared model stops simplifying the system and starts requiring every caller to remember which subtype it is dealing with.
A Separate Admin Area Was Part of the Domain Decision
An editor working with News should not search for a short product update inside a large list of technical Posts.
News therefore received its own admin section with its own list, creation, editing, search, and filters.
News admin capability | Why it exists |
|---|---|
Separate list | Keep two editorial workflows separate |
Status filter | Manage Draft, Published, and scheduled content |
Type filter | Separate product, release, update, and other News |
Publication sorting | News is naturally time-oriented |
Media Picker | Reuse the shared media library |
I Reused ContentBlocks Instead of Building a News Editor
A separate domain was not a reason to invent another long-form content format.
News became a blockable entity and uses the same ContentBlocks pipeline as other supported public content.
News | vshared ContentBlocks | +-- Heading +-- Rich Text +-- Image +-- Code +-- Notice +-- CTA +-- other supported blocksA short excerpt remains useful for cards and collections, while the detail page can contain a full article.
This lets the separate entity evolve without creating a separate content editor.
SEO Did Not Become a Separate News System Either
The same principle applies to SEO.
News has its own public URL and content, but it does not need another implementation of canonical, hreflang, Open Graph, or sitemap generation.
SEO capability | Approach |
|---|---|
Meta title / description | Shared SEO contract |
Canonical | Shared URL infrastructure |
hreflang UK / EN | Existing localization contract |
Open Graph | Shared social metadata pipeline |
Sitemap | Shared indexing infrastructure |
IndexNow | Shared URL submission mechanism |
A separate domain entity does not require a separate SEO framework.
Public Visibility Can Reuse the Same Concepts
Like Post, a News record should not become public simply because it exists in the database.
A normal visitor should see only content that is actually eligible for publication at the current moment.
Draft -> hidden Scheduled before published_at -> hidden Published and eligible -> public Deleted / inactive -> hiddenDraft or scheduled preview remains a protected administrative capability and should not make the content indexable.
A Separate News Entity Does Not Need Separate Analytics
This principle became especially clear when I added analytics for News.
It would have been easy to create a news_analytics table, separate country queries, separate device and browser logic, and another hourly aggregation path.
I did not do that.
Public Analytics | +-- Post +-- Page +-- Post Series +-- NewsNews became another supported public content subject inside the existing analytics architecture.
That gives it views, deduplicated views, daily and hourly trends, countries, devices, browsers, operating systems, traffic sources, locales, and Human/Bot analysis without creating a parallel analytics system.
The Separate Module Gives News Room to Evolve
This is one of the most important reasons for the separation.
If News later gains new types, its own feed rules, additional CTA behavior, or another editorial workflow, those changes do not need to become conditions inside every Post path.
Likewise, changes to Post Series, categories, or long-form article features should not automatically complicate News.
I Split Responsibilities Roughly Like This
Responsibility | Separate or shared |
|---|---|
News model and persistence | Separate |
Post model and persistence | Separate |
News types | Separate |
News Admin | Separate |
Localization | Shared |
Media | Shared |
ContentBlocks | Shared |
SEO infrastructure | Shared |
Publication concepts | Shared rules adapted to the entity |
Public Analytics | Shared |
What Post and News Actually Share
- UK / EN localization;
- Media Picker and Media Usage;
- ContentBlocks and the public renderer;
- SEO metadata and localized URL contracts;
- canonical and hreflang;
- Open Graph and social metadata;
- indexing rules;
- sitemap and IndexNow infrastructure;
- the core concepts around Draft, scheduling, and preview;
- the shared Public Analytics architecture.
When a Separate Module Starts Paying Off
- Editors treat News and Post as different kinds of content.
- News needs its own bounded types instead of Post categories.
- One entity should not inherit features of another only because they share a table.
- Future evolution would keep adding subtype conditionals to a shared model.
- A separate admin workflow makes the system easier to use.
- Common technical capabilities can already be reused through shared services and contracts.
A Separate Module Is Not Always the Right Answer
I would not introduce another domain entity only because a Post needs one more visual presentation style.
If the lifecycle, business rules, admin workflow, and behavior are the same and only the template changes, a separate module may be unnecessary.
For me, the boundary is defined by behavior and responsibility rather than by the number of duplicated columns.
Question | What it helps reveal |
|---|---|
Is the editorial workflow the same? | If not, separation becomes more meaningful |
Will both concepts evolve under the same rules? | If not, one model will accumulate conditionals |
Does it need its own admin area? | This is a strong signal of a separate entity |
Can infrastructure still be reused? | Separate domains do not require duplication |
Is only presentation different? | Then another model may be unnecessary |
Tests Should Protect the Boundary Too
Scenario | What I verify |
|---|---|
Create News | Does not create a Post |
News localization | UK and EN use shared localization infrastructure |
News cover | Uses the shared Media module |
Draft / Scheduled News | Does not become public too early |
News ContentBlocks | Use the shared renderer |
News SEO | Does not require a parallel SEO system |
News Analytics | Uses the existing analytics pipeline |
Post regressions | Adding News does not change Post behavior |
What I Do Not Do in This Architecture
- Do not make News a Post category just because both are content.
- Do not keep adding
if type === newschecks to Post. - Do not create a second Localization system for News.
- Do not build a separate News Media Picker.
- Do not duplicate ContentBlocks for News.
- Do not create News-only SEO infrastructure.
- Do not create separate analytics tables just because another content type appeared.
- Do not force News to inherit Post Series or categories when it does not need them.
- Do not split entities only because their visual design differs.
My Short Checklist Before Creating Another Content Module
- Compare behavior, not only fields.
- Identify which lifecycle rules are actually different.
- Check whether the entity needs a separate editorial workflow.
- Do not confuse code reuse with domain-model reuse.
- Move truly common capabilities into reusable infrastructure.
- Do not duplicate Localization, Media, ContentBlocks, SEO, or Analytics without a reason.
- Give the entity only the capabilities it actually needs.
- Protect domain boundaries with automated tests.
- Ask whether future evolution of one concept would otherwise keep changing the other.
Similar fields are a reason to reuse infrastructure. They are not a requirement to put different domain concepts into one model.
Conclusion
When I added News to ICanUp, technically it would have been easy to make it another Post type.
That would have solved the first version. Every later difference, however, would gradually become another condition in the Post model, queries, validation, admin interface, and public code.
News therefore received its own domain, persistence, bounded types, and editorial section.
At the same time, I did not duplicate the capabilities that genuinely belong to the platform: Localization, Media, ContentBlocks, SEO, publication concepts, and Analytics.
For me, correct separation here does not mean writing everything twice. It means separating entity behavior while reusing infrastructure wherever the infrastructure is genuinely shared.



