In Notion, a paragraph can become a heading, a page can appear inside a database, and a group of blocks can move into another page. Those interactions feel like editing a document, but they rely on a more granular data model than one large text field.
The interesting storage question is how to preserve each piece of information while its presentation, position and permissions change. A flexible editor needs stable identities beneath the visible layout.
Introduction
Notion's engineering team has published explanations of its block model and the evolution of its PostgreSQL storage. These accounts offer a concrete starting point for understanding the product's flexibility.
The model described in the 2021 block article is a historical public account, not a promise that every internal field remains identical today. We will use its central ideas, then develop a simplified project-planning example to explain the design consequences.
The key distinction is between a block's identity, its content, its place in the rendered document and the relationships used for access control. Those concerns interact, but treating them as interchangeable creates subtle bugs.
Blocks Give Information Its Own Identity
Notion's published model represents units such as paragraphs, images and pages as blocks. Blocks have identifiers, types and properties, together with relationships to other blocks. Notion's block data model
Imagine a project page containing a heading, a paragraph and a checklist. In our teaching model, each item has a stable ID. Moving the checklist changes where it appears without requiring it to become a completely new piece of information.
That is useful for references, collaboration and history. Another part of the application can refer to a block even if its text or position changes.
A single giant page string makes those behaviours harder. The system must infer which characters or fragments represent the same item after every edit. Explicit block identity turns that inference into a direct relationship.
Type and Content Can Change Independently
The published block model separates type from properties. Changing how a block is rendered does not necessarily require discarding the information already associated with it.
In our example, converting a paragraph into a heading changes its presentation role while preserving its text and identity. Converting a checklist item into another type raises a further question: what happens to its checked state?
Preserving unused properties can make a later conversion reversible, but it also requires clear rules about which properties each type interprets. Otherwise, old metadata can unexpectedly affect a new presentation.
This is a useful lesson for flexible products: schema flexibility does not remove the need for validation. It moves some validation into the application, where the system must check that a particular combination of type and properties makes sense.
Rendering a Page Means Following Relationships
Notion's account describes ordered content references that determine how nested blocks render. A page is therefore assembled from related records rather than necessarily being retrieved as one pre-rendered document.
For our project page, the parent might reference the heading, paragraph and checklist in that order. The checklist then references its own children. Rendering follows those relationships and interprets each block's type.
This creates a performance concern: one network request per block would make a large page painfully slow. A service should retrieve useful groups of records together and avoid repeatedly fetching the same referenced item.
It also needs limits. A page with thousands of nested items should not force the client to load every hidden section immediately. Bounded retrieval and lazy loading let the interface remain responsive while preserving the underlying structure.
Permissions Are a Separate Structural Concern
In Notion's published explanation, parent pointers support permission inheritance, while content references describe rendering. That distinction is deliberate: knowing where something appears is not always sufficient to determine who may access it.
Consider a conceptual block referenced from more than one place. If each visible location independently granted permission, a harmless display reference could accidentally expose private content.
Our teaching design would define one authoritative permission relationship and validate it on the server. The client may cache access information for responsiveness, but the server cannot trust a client merely claiming that a block is visible.
Moving content is therefore more than a visual operation. It may change the access context. A correct implementation must evaluate the destination's rules, the user's authority to move the content and what existing references should do afterwards.
Creating a Block Often Changes Several Records
Suppose Nina adds a checklist item to the project page. The application needs a new block record and a change to the parent's ordered children. Saving only one of those leaves an inconsistent result.
Our conceptual service groups the related changes into a transaction. Either the new item exists in the intended position, or the operation fails without exposing a half-completed structure.
The server should validate the before and after states. Does the parent still exist? Can Nina edit it? Would the change create an invalid cycle or place an item where its type is not supported?
Optimistic client rendering can make the item appear immediately, but the client must still reconcile with the server's decision. A fast local update is a user-experience technique, not proof that the change has been accepted durably.
PostgreSQL Can Store a Flexible Product Model
Notion's sharding publication describes scaling PostgreSQL storage for its growing data. A flexible block-oriented product does not automatically require abandoning relational databases. Notion's PostgreSQL sharding account
In a teaching implementation, relational columns can hold stable routing and identity fields while structured properties hold type-dependent values. The exact division depends on the queries that need efficient indexes and constraints.
For example, workspace identity, block identity and version may deserve explicit indexed fields. Rich text formatting may be better represented as structured content rather than dozens of unrelated columns.
The practical question is which operations must be fast and enforceable. “Flexible schema” is not a reason to make every important lookup inspect arbitrary JSON. Equally, forcing every presentation property into a separate table can create unnecessary joins and migration work.
Workspace-Based Sharding Has Benefits and Limits
Notion's later re-sharding account describes workspace-based partitioning and the operational work involved in changing the distribution of its data. Keeping related workspace records together can reduce cross-database operations. Notion's re-sharding case study
For our project workspace, page edits often involve records within the same organisational boundary. Co-location can make those transactions easier to execute.
The tradeoff is imbalance. A very large or active workspace can generate more load than many ordinary workspaces combined. The number of workspaces per shard is therefore a poor substitute for measuring actual storage and query pressure.
Routing must also remain stable during migration. A request should not write to the old location while a later read assumes the new location contains all changes. Moving a shard is a coordinated data operation, not simply copying a directory and changing a connection string.
A Notion Database Is Also a Product Concept
The word database has two meanings here. Users see a structured collection of pages with properties and views. Engineers also use databases as the physical systems that persist records.
Those are not the same layer. Creating a new table view in the product does not imply provisioning a new PostgreSQL database server or creating a physical SQL table dedicated to that view.
In our example, a tasks collection might contain pages with status, owner and due-date properties. A board view groups those records by status; a calendar view arranges them by date. Both can present the same underlying items.
This separation avoids duplicating task content for every visualisation. It also means a view's filters and sorting are themselves data that must be stored, validated and applied consistently when the underlying pages change.
Concurrent Editing Requires an Explicit Merge Policy
Two colleagues can modify the same project while holding different local views. Stable block IDs help identify what each edit targets, but they do not automatically resolve conflicting intentions.
Imagine Nina changes a task's title while Omar changes its status. Those independent properties can often be merged. If both move the same block to different parents, the system needs a clear ordering or conflict rule.
Text editing introduces additional complexity because two changes can target overlapping character ranges. A collaborative editor needs a defined algorithm for representing and combining those operations; treating the whole text field as last-write-wins can discard substantial work.
This section describes the general problem rather than asserting one current Notion collaboration algorithm. The design lesson is that a database transaction protects stored invariants, while a collaboration policy determines how multiple valid user intentions are reconciled.
Local State and Server State Have Different Jobs
A responsive editor should not wait for a round trip before drawing every keystroke. Our example client can apply a change locally, place it in a durable pending queue and send it to the server.
That queue needs stable operation identities so retries do not create duplicate blocks. It also needs to survive an application restart if the interface promises that pending edits are retained.
After reconnecting, the client must reconcile pending operations with newer server state. A block may have moved, been deleted or become inaccessible while the device was disconnected. Blindly uploading the entire old page would overwrite other people's work.
The product should distinguish local acceptance from server confirmation. Users do not need implementation details, but they do need an accurate indication when work is still waiting to sync or requires attention.
Moving and Deleting Blocks Need Lifecycle Rules
Moving a nested section may affect its parent relationship, display order and permission context. Our teaching service should update the necessary records as one coherent operation and reject moves that would create cycles.
Deletion raises a different question: should a removed block disappear immediately from every reference, or remain as a recoverable tombstone? The choice affects undo, collaboration and history.
A delayed operation must not accidentally resurrect a deleted block unless restoration is explicitly intended. Version checks or deletion state can distinguish an old edit from a genuine restore action.
File attachments and search entries also need cleanup. Deleting a visible page is not proof that every derived representation has been removed. The application needs a lifecycle that follows the block beyond the editor's current screen.
Search and Reporting Should Not Burden Every Edit
Searching across a workspace requires a different view from loading one known page. A conceptual search service can index block text and relevant metadata, with current permissions applied before returning results.
Analytics likewise benefits from a separate processing path. Notion's data-lake account describes moving operational data into analytical infrastructure, illustrating that transactional editing and broad analysis have different needs. Notion's data-lake engineering
For our smaller application, the important boundary is that an expensive report should not hold up a user's checklist edit. Change events can feed derived systems asynchronously, provided their lag and failure handling are visible.
Derived systems must also understand deletion and schema evolution. A new block type should not silently vanish from reports simply because an older consumer does not recognise its properties.
What to Test in a Block-Based Editor
Test adding a child while another user deletes the parent, moving a block while permissions change and retrying a transaction after losing the response. Each exposes assumptions that a single-user demo will not reveal.
Also test very deep nesting and unusually large pages. Correctness includes bounding the amount of work needed to render or validate a structure. A cycle or extreme depth should not crash the service through uncontrolled recursion.
For a first implementation, use simple types, explicit relationships and ordinary transactions. Add more flexible transformations only after defining how they preserve identity and permissions. Flexibility is most useful when the system can still explain what each operation means.
The Big Picture
Notion's published architecture shows how stable blocks, explicit relationships and scalable relational storage can support a flexible document experience. Pages and views are assembled from information that has its own identity.
For engineers, the main lesson is to separate content from presentation without losing structure. Every move, conversion and collaborative edit still needs clear rules for ordering, permissions and persistence.
Once those rules are explicit, a product can feel fluid while the storage beneath it remains understandable, testable and recoverable.
