This repository is the source of truth for all written content and structured metadata for ollieread.com.
- Write everything in plain Markdown + YAML
- Keep content versioned in Git
- Keep structure machine-readable
- Minimise duplication (especially for Laravel release/change coverage)
articles/ # Standalone articles
<slug>.md # Single-file format (Markdown with front matter)
<slug>/ # Directory format
article.yaml # Article metadata
article.md # Article content (no front matter)
_notes.md # Optional personal notes (not published)
series/ # Series definitions (YAML)
courses/ # Mini-courses (directories)
<course-slug>/
course.yaml # Course metadata
01-chapter-slug.md # Course chapters (Markdown)
02-another-chapter.md
_notes.md # Optional personal notes (not published)
knowledgebase/ # Short informational snippets
<slug>.md # Single-file format (Markdown with front matter)
projects/ # Software projects
<slug>/ # Directory format only
project.yaml # Project metadata
project.md # Project description/overview (no front matter)
_notes.md # Optional personal notes (not published)
changes/ # Atomic meaningful changes
<org>/
<repo>/
<change-id>.yaml # Single-file format
<change-id>.brief.md # Optional short writeup (single-file)
<change-id>.deep.md # Optional detailed writeup (single-file)
<change-id>/ # Directory format
change.yaml # Change metadata
brief.md # Optional short writeup
deep.md # Optional detailed writeup
_notes.md # Optional personal notes (not published)
changesets/ # Per-version collections
<org>/
<repo>/
<version>.yaml
categories.yaml # Article categories (format taxonomy)
topics.yaml # Topic hierarchy (subject taxonomy)
| Concept | Purpose | Location |
|---|---|---|
| Article | A single published piece of content | articles/<slug>.md |
| Series | An ordered collection of related articles | series/<slug>.yaml |
| Course | A structured mini-course with chapters | courses/<slug>/ |
| Knowledgebase | Atomic snippets connected via typed links and topics | knowledgebase/<slug>.md |
| Project | A software project you've built or maintain | projects/<slug>/ |
| Category | What kind of article it is (format/intent) | categories.yaml |
| Topic | What the content is about (subject) | topics.yaml |
| Change | A single meaningful change to a project | changes/<org>/<repo>/<id>.yaml |
| Changeset | All changes that shipped in a version | changesets/<org>/<repo>/<version>.yaml |
Category vs Topic: Categories describe the format or editorial intent (e.g., "deep-dive", "tutorial", "release notes"). Topics describe the subject matter (e.g., "laravel/http/routing"). An article has exactly one category but can have multiple topics.
Change vs Changeset: A Change is a single semantic change (which may span multiple commits/PRs). A Changeset is the collection of all changes that shipped in a specific version. This separation allows the same change to appear in multiple versions (backports) without duplication.
Series vs Course: A Series is a loose collection of related articles that can be read independently. A Course is a structured learning path with ordered chapters that build on each other.
Article vs Knowledgebase: An Article is a full piece of content with narrative structure, context, and depth. A Knowledgebase entry is an atomic snippet — a single concept, technique, or piece of information — connected to other entries via typed links and topics to form a navigable graph. Think of articles as "posts" and knowledgebase entries as "nodes in a wiki".
Project vs Article: A Project is a structured reference page for a piece of software you've built or maintain. It is
not narrative content — it's metadata-driven, with links, version info, and connections to related content. Articles,
series, knowledgebase entries, and courses can belong to a project via the relationships.project field.
All authored content types (articles, series, courses, knowledgebase entries, and projects) share a common set of field groups. These are defined once here and referenced by each content type. Changes and changesets use some of the same formats but have their own schema structure.
Each group is a YAML object. Content types may extend a group with type-specific fields nested alongside the shared ones.
Controls how the content appears on the site. Every authored content type has this group.
content:
slug: my-content-slug
status: published
summary: >
A short description for cards, listings, and search results.| Field | Type | Required | Description |
|---|---|---|---|
slug |
string | ✓ | URL slug. Should match the filename or directory name. |
status |
enum | ✓ | Publication status (see below) |
summary |
string | Short description for cards/listings (1-2 sentences) |
These values apply to all content types and describe whether the content is visible on the site:
| Value | Description |
|---|---|
draft |
Work in progress, not publicly visible |
published |
Live and publicly accessible |
archived |
No longer promoted but still accessible |
unlisted |
Accessible via direct link only |
Not all content types use every value. Series and courses typically use draft, published, and archived.
Knowledgebase entries use draft, published, and archived.
Content types may add fields to this group:
| Field | Used by | Description |
|---|---|---|
category |
Article | Category ID from categories.yaml |
kind |
Knowledgebase | What type of snippet (tip, pattern, gotcha, etc.) |
tagline |
Project | One-liner subtitle for display beneath the title |
Describes what the content is about and provides filtering facets.
classification:
topics:
- laravel/http/routing
- laravel/database/eloquent
tags:
- routing
- eloquent| Field | Type | Description |
|---|---|---|
topics |
array | Topic paths from topics.yaml. Assign the most specific (leaf) topics; parent topics are implicit. |
tags |
array | Flat, cross-cutting labels for filtering. Don't duplicate information already captured by topics. |
Tracks where the knowledge comes from and which versions it applies to.
provenance:
versions:
laravel/framework: ">=10.0"
php: ">=8.1"
sources:
- type: pr
repo: laravel/installer
ref: "390"| Field | Type | Description |
|---|---|---|
versions |
object | Version constraints by package (see below) |
sources |
array | Links to PRs, commits, issues that this content derives from (see below) |
Both fields are optional. Use versions when the content is version-specific. Use sources when the content is
traceable to specific code changes.
The versions field maps package names to Composer-style version constraints:
provenance:
versions:
laravel/framework: ">=10.0"
laravel/installer: ">=5.12.0 <=5.16.0"
php: ">=8.2"Common constraint formats:
>=10.0— version 10.0 or higher^10.0— version 10.x (semver compatible)>=10.0 <12.0— between versions (exclusive upper bound)>=5.12.0 <=5.16.0— between versions (inclusive upper bound)*— all versions
| Field | Type | Required | Description |
|---|---|---|---|
type |
enum | ✓ | Type of source reference |
repo |
string | ✓ | Repository in org/repo format |
ref |
string | ✓ | Reference (PR number, commit SHA, compare range) |
note |
string | Additional context |
| Value | Description | Example ref |
|---|---|---|
pr |
Pull request | 390 |
commit |
Single commit | abc123def456 |
compare |
Comparison range | v12.0.0...v12.1.0 |
issue |
GitHub issue | 9876 |
Links this content to other content in the repository.
relationships:
project: sprout-for-laravel
related:
- slug: service-container-basics
type: knowledgebase
rel: builds-on
- slug: understanding-multitenancy-patterns
type: article
rel: see-also| Field | Type | Description |
|---|---|---|
project |
string | Project slug if this content belongs to a project |
related |
array | Typed links to other content (see below) |
When a series has a project field, its articles inherit the project association. Articles may also declare project
explicitly. If both the article and its series specify a project, the article's value takes precedence.
The publishing system should collect all content associated with a project by:
- Finding all content with an explicit
relationships.projectfield matching the project slug - Finding all content belonging to a series that has a matching
relationships.projectfield - Combining these with the project's own
relationships.relatedentries - Deduplicating (explicit
projectfield content takes priority overrelated)
Each entry in related is an object:
| Field | Type | Required | Description |
|---|---|---|---|
slug |
string | ✓ | Slug of the linked content |
type |
enum | Content type of the target. Defaults to the current content type if omitted. | |
rel |
enum | Relationship type. Defaults to related if omitted. |
Content type values: article, series, course, knowledgebase, project
| Value | Description |
|---|---|
builds-on |
This entry assumes knowledge from the linked entry |
extends |
This entry adds to the linked entry, without requiring it |
alternative |
A different approach to the same problem |
related |
General connection |
supersedes |
This entry replaces the linked entry |
corrects |
This entry fixes a misconception from the linked entry |
see-also |
Loosely related, "you might also want this" |
These relationships are directional — builds-on means this entry builds on that one. The publishing system can
infer reverse relationships for graph traversal (e.g., if A builds-on B, then B is a prerequisite for A).
Tracks when content was created and last updated, plus type-specific dates.
dates:
created_at: 2026-01-22
updated_at: 2026-01-25| Field | Type | Description |
|---|---|---|
created_at |
date | ISO date when first created |
updated_at |
date | ISO date of last significant update |
| Field | Used by | Description |
|---|---|---|
published_at |
Article | ISO date when first published |
started_at |
Project | ISO date when the project started |
An article is a single published piece of content. Articles can be stored in two formats: a single Markdown file with front matter, or a directory with separate metadata and content files.
Single-file format:
articles/<slug>.md
Directory format:
articles/<slug>/
article.yaml # Article metadata
article.md # Article content (no front matter)
_notes.md # Optional personal notes (not published)
The filename or directory name should match the slug in the metadata.
Use the directory format when you want to keep notes alongside an article, or when the front matter becomes unwieldy.
Articles use all shared field groups plus these article-specific fields:
title: Laravel's Route Model Binding
content:
slug: laravels-route-model-binding
status: published
summary: >
A source-driven walkthrough of how Laravel resolves
route parameters into model instances.
category: deep-dive
classification:
topics:
- laravel/http/routing/route-model-binding
- laravel/container/binding
tags:
- routing
- eloquent
- internals
provenance:
versions:
laravel/framework: ">=11.0"
sources:
- type: pr
repo: laravel/framework
ref: "48210"
relationships:
project: sprout-for-laravel
related:
- slug: manual-service-resolution-in-laravel
type: article
rel: see-also
dates:
created_at: 2026-01-22
published_at: 2026-01-22
updated_at: 2026-01-25
# Article-specific
canonical_url: https://example.com/original-post| Field | Type | Description |
|---|---|---|
content.category |
string | Required. Category ID from categories.yaml |
canonical_url |
string | Original URL if republished from elsewhere |
dates.published_at |
date | ISO date when first published |
title, content.slug, content.status, content.category
---
title: Laravel's Route Model Binding
content:
slug: laravels-route-model-binding
status: published
summary: >
A source-driven walkthrough of how Laravel resolves
route parameters into model instances.
category: deep-dive
classification:
topics:
- laravel/http/routing/route-model-binding
- laravel/container/binding
tags:
- routing
- eloquent
dates:
created_at: 2026-01-20
published_at: 2026-01-22
updated_at: 2026-01-25
---
Laravel has many undocumented features...articles/laravels-route-model-binding/article.yaml
title: Laravel's Route Model Binding
content:
slug: laravels-route-model-binding
status: published
summary: >
A source-driven walkthrough of how Laravel resolves
route parameters into model instances.
category: deep-dive
classification:
topics:
- laravel/http/routing/route-model-binding
- laravel/container/binding
tags:
- routing
- eloquent
dates:
created_at: 2026-01-20
published_at: 2026-01-22
updated_at: 2026-01-25
articles/laravels-route-model-binding/article.md
Laravel has many undocumented features...articles/laravels-route-model-binding/_notes.md
## Research notes
- Check if this changed in Laravel 11
- Link to Taylor's PR comment about the naming
- TODO: Add diagram of resolution flowThe _notes.md file is for personal notes and research — it is not published or processed.
A series is an ordered collection of related articles. Articles in a series can still be read independently but are presented together as a cohesive set.
series/<slug>.yaml
Series use the shared field groups plus these series-specific fields:
name: hidden-parts-of-laravel
title: The Hidden Parts of Laravel
content:
slug: hidden-parts-of-laravel
status: published
summary: >
Exploring Laravel's undocumented features and hidden functionality.
classification:
topics:
- laravel/internals
relationships:
project: sprout-for-laravel
dates:
created_at: 2026-01-10
# Series-specific
image: series/hidden-parts-of-laravel.png
series_status: ongoing
articles:
- laravels-route-model-binding
- manual-service-resolution-in-laravel
- laravel-middleware-priority| Field | Type | Required | Description |
|---|---|---|---|
name |
string | ✓ | Internal identifier (immutable, usually matches slug) |
image |
string | Path to series cover image | |
series_status |
enum | Completion status of the series | |
articles |
array | ✓ | Ordered list of article slugs |
name, title, content.slug, articles
The series_status field describes the completion state of the series, separate from whether it is published on the
site (content.status):
| Value | Description |
|---|---|
ongoing |
New articles may be added |
complete |
Series is finished |
abandoned |
No longer being updated |
name: hidden-parts-of-laravel
title: The Hidden Parts of Laravel
content:
slug: hidden-parts-of-laravel
status: published
summary: >
Exploring Laravel's undocumented features and hidden functionality
that you won't find in the official docs.
classification:
topics:
- laravel/internals
- laravel/undocumented
dates:
created_at: 2026-01-10
image: series/hidden-parts-of-laravel.png
series_status: ongoing
articles:
- laravels-route-model-binding
- manual-service-resolution-in-laravel
- laravel-middleware-priority
- decorating-services-in-laravelA course is a structured mini-course with ordered chapters. Unlike a series, courses are designed to be followed sequentially and may be gated (paid/premium).
courses/<course-slug>/
course.yaml # Course metadata
01-introduction.md # First chapter
02-next-chapter.md # Second chapter
...
_notes.md # Optional personal notes (not published)
Chapter filenames should be prefixed with numbers for sorting (e.g., 01-, 02-).
The _notes.md file is for personal notes and research — it is not published or processed.
Courses use the shared field groups plus these course-specific fields:
name: laravel-app-bootstrapping
title: Laravel Bootstrapping Internals
content:
slug: laravel-app-bootstrapping
status: published
summary: >
A short, practical course covering the modern Laravel bootstrap pipeline.
classification:
topics:
- laravel/lifecycle/bootstrapping
- laravel/http/kernel
relationships:
project: sprout-for-laravel
dates:
created_at: 2026-02-01
# Course-specific
level: advanced
price_gbp: 29
image: courses/laravel-bootstrapping.png
prerequisites:
- laravel-service-container-basics
chapters:
- 01-introduction.md
- 02-the-bootstrap-pipeline.md
- 03-middleware-assembly.md
- 04-request-lifecycle.md| Field | Type | Required | Description |
|---|---|---|---|
name |
string | ✓ | Internal identifier (immutable) |
level |
enum | Difficulty level | |
price_gbp |
number | Price in GBP (omit for free) | |
image |
string | Path to course cover image | |
prerequisites |
array | Course slugs that should be completed first | |
chapters |
array | ✓ | Ordered list of chapter filenames |
name, title, content.slug, chapters
| Value | Description |
|---|---|
beginner |
No prior knowledge required |
intermediate |
Assumes basic framework familiarity |
advanced |
Requires solid understanding of concepts |
Chapters are Markdown files with optional front matter.
---
title: The Bootstrap Pipeline
slug: the-bootstrap-pipeline
summary: How Laravel assembles and executes its bootstrap sequence.
---
Chapter content here...| Field | Type | Required | Description |
|---|---|---|---|
title |
string | Chapter title (defaults to filename) | |
slug |
string | URL slug (defaults to filename without prefix) | |
summary |
string | Short description | |
draft |
boolean | If true, chapter is not yet ready |
If front matter is omitted entirely, the chapter title is derived from the filename (e.g.,
02-the-bootstrap-pipeline.md → "The Bootstrap Pipeline").
courses/laravel-app-bootstrapping/course.yaml
name: laravel-app-bootstrapping
title: Laravel Bootstrapping Internals
content:
slug: laravel-app-bootstrapping
status: published
summary: >
A short, practical course covering the modern Laravel bootstrap pipeline,
from entry point to first controller.
classification:
topics:
- laravel/lifecycle/bootstrapping
- laravel/http/kernel
dates:
created_at: 2026-02-01
level: advanced
price_gbp: 29
image: courses/laravel-bootstrapping.png
prerequisites:
- laravel-service-container-basics
chapters:
- 01-introduction.md
- 02-the-bootstrap-pipeline.md
- 03-middleware-assembly.md
- 04-request-lifecycle.mdcourses/laravel-app-bootstrapping/01-introduction.md
---
title: Introduction
summary: What we'll cover and why it matters.
---
Every Laravel request follows a specific path through the framework...A Knowledgebase entry is an atomic snippet of knowledge — a single concept, technique, gotcha, or piece of reference information. Entries are connected to each other via typed links and to the broader content via topics, forming a navigable graph of lookup-friendly knowledge.
Unlike articles, knowledgebase entries are not narrative. They're designed to be found, read quickly, and traversed. The value comes from the connections as much as the individual entries.
Use knowledgebase entries for:
- Quick reference notes about framework features
- Code patterns and idioms
- Gotchas and common pitfalls
- Configuration snippets with explanation
- Definitions of terms and concepts
- Worked examples of specific techniques
Knowledgebase entries use the single-file format only:
knowledgebase/<slug>.md
Knowledgebase entries use all shared field groups plus content.kind:
---
title: Extending Services with afterResolving
content:
slug: extending-services-with-after-resolving
status: published
summary: >
How to safely extend Laravel services using the container's
afterResolving callback.
kind: pattern
classification:
topics:
- laravel/container/resolution
- laravel/package-development
tags:
- dependency-injection
- service-providers
provenance:
versions:
laravel/framework: ">=9.0"
sources:
- type: pr
repo: laravel/framework
ref: "41205"
relationships:
project: sprout-for-laravel
related:
- slug: service-container-basics
rel: builds-on
- slug: creating-custom-drivers
rel: see-also
- slug: extending-via-macros
rel: alternative
dates:
created_at: 2026-01-22
updated_at: 2026-01-25
---| Field | Type | Required | Description |
|---|---|---|---|
content.kind |
enum | ✓ | What type of snippet this is |
title, content.slug, content.status, content.kind
| Value | Description |
|---|---|
tip |
A quick, actionable piece of advice |
pattern |
A reusable code pattern or idiom |
gotcha |
A common pitfall or surprising behaviour |
reference |
Factual reference information |
snippet |
A useful code snippet with context |
example |
A worked example of a specific technique |
definition |
Explanation of a term or concept |
cheatsheet |
A quick-reference table or list |
In knowledgebase entries, type defaults to knowledgebase when omitted, so entries linking to other entries only need
slug and rel:
relationships:
related:
- slug: service-container-basics
rel: builds-on
- slug: understanding-multitenancy-patterns
type: article
rel: see-alsoknowledgebase/laravel-installer-defaults-to-sqlite-for-starter-kits.md
---
title: Laravel installer defaults to SQLite for starter kits
content:
slug: laravel-installer-defaults-to-sqlite-for-starter-kits
status: draft
summary: >
When installing Laravel through the installer, using a starter kit,
it will always overwrite that starter kit's default database settings
to use SQLite, unless you explicitly specify the database.
kind: gotcha
classification:
topics:
- laravel/getting-started
tags:
- laravel-installer
- starter-kit
- sqlite
provenance:
versions:
laravel/installer: ">=5.12.0"
sources:
- type: pr
repo: laravel/installer
ref: "390"
dates:
created_at: 2026-02-14
---
Starting with version `5.12` of the Laravel installer, the database will always
be SQLite...A Project is a piece of software you've built or maintain — an open-source package, a tool, a product. Each project gets its own page with a description, links, metadata, and connections to related content across the repo.
Projects differ from articles in that they're not narrative content — they're structured reference pages for things you' ve made. The content body is a freeform description/overview, but the metadata carries the heavy lifting for display, linking, and discoverability.
Projects have two separate status concepts: publication status (content.status) controls whether the listing is
visible on the site, while lifecycle describes the current state of the software itself.
Projects always use the directory format:
projects/<slug>/
project.yaml # Project metadata
project.md # Project description/overview (no front matter)
_notes.md # Optional personal notes (not published)
The directory name should match the slug in the metadata.
Projects use all shared field groups plus project-specific fields:
title: Sprout for Laravel
content:
slug: sprout-for-laravel
status: published
summary: >
Sprout is an easy to use multitenancy solution that integrates
seamlessly with your Laravel application, while remaining flexible.
tagline: Multitenancy for your Laravel application
classification:
topics:
- laravel/multitenancy
- laravel/package-development
tags:
- laravel
- multitenancy
- open-source
provenance:
versions:
laravel/framework: ">=10.0"
php: ">=8.2"
relationships:
related:
- slug: understanding-multitenancy-patterns
type: article
rel: see-also
- slug: tenant-identification-strategies
type: knowledgebase
rel: see-also
- slug: multitenancy-fundamentals
type: series
rel: see-also
dates:
created_at: 2024-03-15
started_at: 2024-03-15
# Project-specific
lifecycle: active
links:
website: https://sprout.ollieread.com
docs: https://sprout.ollieread.com/docs/1.x/installation
github: https://github.com/sprout-laravel
packagist: https://packagist.org/packages/sprout-laravel/sprout
current_version: "1.1"
image: projects/sprout-for-laravel.png| Field | Type | Required | Description |
|---|---|---|---|
content.tagline |
string | One-liner subtitle for display beneath the title | |
lifecycle |
enum | ✓ | Current state of the software |
links |
object | Named external links (freeform keys, URL values) | |
current_version |
string | Latest stable version | |
image |
string | Path to project image/logo | |
dates.started_at |
date | ISO date when the project started |
title, content.slug, content.status, lifecycle
The lifecycle field describes the state of the software itself, independent of whether the listing is visible on the
site:
| Value | Description |
|---|---|
active |
Actively maintained and developed |
stable |
Maintained but not actively adding features |
experimental |
Early stage, API may change |
archived |
No longer maintained |
The links field is a flat object of named URLs. Keys are freeform, but these are conventional:
| Key | Description |
|---|---|
website |
Project homepage |
docs |
Documentation |
github |
GitHub repository |
packagist |
Packagist listing |
npm |
npm listing |
demo |
Live demo |
Any key is valid — the publishing system should render them all.
projects/sprout-for-laravel/project.yaml
title: Sprout for Laravel
content:
slug: sprout-for-laravel
status: published
summary: >
Sprout is an easy to use multitenancy solution that integrates
seamlessly with your Laravel application, while remaining flexible.
tagline: Multitenancy for your Laravel application
classification:
topics:
- laravel/multitenancy
- laravel/package-development
tags:
- laravel
- multitenancy
- open-source
provenance:
versions:
laravel/framework: ">=10.0"
php: ">=8.2"
relationships:
related:
- slug: understanding-multitenancy-patterns
type: article
rel: see-also
- slug: tenant-identification-strategies
type: knowledgebase
rel: see-also
dates:
created_at: 2024-03-15
started_at: 2024-03-15
lifecycle: active
current_version: "1.1"
image: projects/sprout-for-laravel.png
links:
website: https://sprout.ollieread.com
docs: https://sprout.ollieread.com/docs/1.x/installation
github: https://github.com/sprout-laravel
packagist: https://packagist.org/packages/sprout-laravel/sproutprojects/sprout-for-laravel/project.md
Sprout is a multitenancy package for Laravel that takes a different approach
to most existing solutions. Rather than forcing a specific tenancy strategy,
it provides the building blocks to implement whichever approach fits your
application best.
## Key Features
- Seamless integration with Laravel's service container
- Support for multiple tenancy strategies (domain, path, header, etc.)
- Database-per-tenant and shared-database support
- Tenant-aware queue jobs and scheduled tasksprojects/sprout-for-laravel/_notes.md
## TODO
- Add migration guide from v1.0 to v1.1
- Screenshot of dashboard for image field
- Link to the devlog series once it's startedThe _notes.md file is for personal notes and research — it is not published or processed.
Categories describe what kind of thing an article is — the format or editorial intent, not the subject matter.
An article has exactly one category.
categories.yaml
<category-id>:
name: Category Name
slug: category-slug
description: What this category represents.
title: Display Title # Optional
sort_order: 0 # Optional
is_visible: true # Optional
icon: icon-name # Optional
color: "#hex" # Optional| Field | Type | Required | Description |
|---|---|---|---|
name |
string | ✓ | Display name |
slug |
string | ✓ | URL slug |
description |
string | Explanation of what this category represents | |
title |
string | Optional display title override | |
sort_order |
integer | Position in ordered listings | |
is_visible |
boolean | Whether the category appears in listings | |
icon |
string | Icon identifier for UI | |
color |
string | Hex colour for UI |
meta:
name: Meta
slug: meta
description: Posts about the site, the content, and the process.
sort_order: 0
is_visible: true
deep-dive:
name: Deep Dive
slug: deep-dive
description: In-depth, source-driven exploration of framework internals.
sort_order: 3
is_visible: true
icon: microscope
color: "#6366f1"Topics describe what the content is about — the subject matter. Topics are hierarchical (a tree structure) and referenced by their full path.
Content can have multiple topics. Assign the most specific (leaf) topics; parent topics are implicit.
topics.yaml
Topics are nested objects. Each level can have:
<topic-id>:
name: Topic Name
description: What this topic covers.
children:
<child-topic-id>:
name: Child Topic
children:
...| Field | Type | Required | Description |
|---|---|---|---|
name |
string | ✓ | Display name |
description |
string | Explanation of what this topic covers | |
children |
object | Nested child topics |
Topics are referenced by their path from root to leaf, separated by /:
laravel/http/routing/route-model-binding
When assigning topics to content, use the most specific path. The publishing system should automatically associate content with parent topics.
laravel:
name: Laravel
description: The Laravel PHP framework.
children:
http:
name: HTTP Layer
description: Request handling, routing, and responses.
children:
routing:
name: Routing
description: URL routing and route configuration.
children:
route-model-binding:
name: Route Model Binding
description: Automatic model resolution from route parameters.
route-parameters:
name: Route Parameters
description: Capturing and constraining URL segments.
url-generation:
name: URL Generation
description: Generating URLs to named routes.
middleware:
name: Middleware
description: Request/response pipeline filters.
children:
middleware-priority:
name: Middleware Priority
description: Controlling middleware execution order.
global-middleware:
name: Global Middleware
description: Middleware applied to all requests.
requests:
name: Requests
description: HTTP request handling and input.
responses:
name: Responses
description: HTTP response generation.
container:
name: Service Container
description: Dependency injection and service resolution.
children:
binding:
name: Binding
description: Registering services in the container.
resolution:
name: Resolution
description: Resolving services from the container.
lifecycle:
name: Application Lifecycle
description: Bootstrapping and request lifecycle.
children:
bootstrapping:
name: Bootstrapping
description: Application initialization process.
service-providers:
name: Service Providers
description: Registering and booting application services.
php:
name: PHP
description: PHP language features and patterns.
children:
attributes:
name: Attributes
description: PHP 8+ attributes and metadata.
enums:
name: Enums
description: PHP 8.1+ enumerations.A Change represents a single meaningful change to a project (framework, app skeleton, package). It is the atomic unit of "what changed" and can be referenced by multiple changesets.
Changes use the same topics, tags, and sources formats defined in Shared Fields, but have their own schema
structure rather than the grouped field objects used by authored content types.
Single-file format:
changes/<org>/<repo>/<change-id>.yaml
changes/<org>/<repo>/<change-id>.brief.md # Optional short writeup
changes/<org>/<repo>/<change-id>.deep.md # Optional detailed writeup
For the single-file format, brief and deep writeups are companion files that sit alongside the YAML file, sharing the same base name.
Directory format:
changes/<org>/<repo>/<change-id>/
change.yaml # Change metadata
brief.md # Optional short writeup (2-6 lines)
deep.md # Optional detailed writeup
_notes.md # Optional personal notes (not published)
Use the directory format when you want to keep notes alongside the change, or when you prefer the files grouped together.
Examples:
changes/laravel/framework/collection-lazy-by-default.yamlchanges/laravel/framework/collection-lazy-by-default.brief.mdchanges/laravel/laravel/public-disk-url-fallback/change.yaml
# Required
name: collection-lazy-by-default
project: laravel/framework
title: Collections use lazy evaluation by default
# Classification (all required)
type: feature
scope: runtime
impact: high
audience: broad
magic: low
# Recommended
summary: >
The Collection class now defers evaluation until iteration,
improving memory usage for large datasets.
# Optional
topics:
- laravel/collections
tags:
- laravel-12
- performance
- breaking-change
deprecated: false
breaking: true
security: false
# Sources (at least one recommended)
sources:
- type: pr
repo: laravel/framework
ref: "12345"
- type: commit
repo: laravel/framework
ref: abc123def456Note: On changes, project refers to the repository in org/repo format — this is different from
relationships.project on authored content types, which refers to a project slug within this repository.
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | ✓ | Unique identifier (should be descriptive and stable) |
project |
string | ✓ | Repository in org/repo format |
title |
string | ✓ | Human-readable title of the change |
type |
enum | ✓ | Type of change |
scope |
enum | ✓ | What part of the system it affects |
impact |
enum | ✓ | How significantly it affects users |
audience |
enum | ✓ | How many users it affects |
magic |
enum | ✓ | How "magical" or implicit the behaviour is |
summary |
string | One-paragraph explanation | |
topics |
array | Related topic paths (same format as Shared Fields) | |
tags |
array | Cross-cutting labels (same format as Shared Fields) | |
deprecated |
boolean | Marks something as deprecated | |
breaking |
boolean | Is this a breaking change? | |
security |
boolean | Is this a security fix? | |
sources |
array | Links to commits, PRs, comparisons (same format as Shared Fields) |
| Value | Description |
|---|---|
feature |
New functionality |
bugfix |
Bug fix |
perf |
Performance improvement |
dx |
Developer experience improvement |
refactor |
Internal refactoring (no behaviour change) |
docs |
Documentation change |
security |
Security fix |
deprecation |
Deprecates existing functionality |
| Value | Description |
|---|---|
install |
Affects fresh installations only |
runtime |
Affects running applications |
tooling |
Affects CLI tools, commands, dev workflow |
docs |
Documentation only |
tests |
Test suite only |
| Value | Description |
|---|---|
low |
Minimal effect, most users won't notice |
medium |
Noticeable improvement or change |
high |
Significant change that many users will want to know about |
| Value | Description |
|---|---|
niche |
Affects a small subset of users |
some |
Affects users of specific features |
broad |
Affects most users |
| Value | Description |
|---|---|
low |
Explicit, predictable behaviour |
medium |
Some implicit behaviour |
high |
Significant implicit/automatic behaviour |
changes/laravel/laravel/public-disk-url-app-url-fallback.yaml
name: public-disk-url-app-url-fallback
project: laravel/laravel
type: bugfix
scope: install
impact: medium
audience: broad
magic: low
title: Public disk URL now trims APP_URL and falls back safely
summary: >
Prevents malformed `//storage` URLs and avoids null deprecation
warnings when APP_URL is missing.
topics:
- laravel/config/filesystems
tags:
- laravel-12
- config
- filesystems
breaking: false
security: false
sources:
- type: commit
repo: laravel/laravel
ref: abcdef123456
- type: compare
repo: laravel/laravel
ref: v12.11.0...v12.11.2changes/laravel/laravel/public-disk-url-app-url-fallback.brief.md
The `public` disk now properly handles missing or malformed `APP_URL` values. Previously, an unset `APP_URL` would cause
deprecation warnings, and URLs with trailing slashes would produce `//storage` paths.changes/laravel/framework/collection-lazy-by-default/change.yaml
name: collection-lazy-by-default
project: laravel/framework
type: feature
scope: runtime
impact: high
audience: broad
magic: medium
title: Collections use lazy evaluation by default
summary: >
The Collection class now defers evaluation until iteration,
improving memory usage for large datasets.
topics:
- laravel/collections
tags:
- laravel-12
- performance
- breaking-change
breaking: true
security: false
sources:
- type: pr
repo: laravel/framework
ref: "54321"changes/laravel/framework/collection-lazy-by-default/brief.md
Collections now use lazy evaluation by default. Operations like `map`, `filter`, and `transform` are deferred until the
collection is iterated.changes/laravel/framework/collection-lazy-by-default/deep.md
## What Changed
Previously, every Collection method that transformed data would execute immediately and allocate a new array. With this
change, transformation methods return a lazy wrapper that only processes elements as they're consumed.
## Why It Matters
For large datasets, this dramatically reduces memory usage. A chain like `$collection->map(...)->filter(...)->take(10)`
now only processes the elements needed to produce 10 results, rather than transforming the entire collection twice.
## Migration Notes
If your code relies on side effects during collection operations, you may need to add `->all()` or `->values()` to force
eager evaluation.changes/laravel/framework/collection-lazy-by-default/_notes.md
## Research
- Taylor mentioned this was inspired by Rust iterators
- Check if this affects `Collection::macro()` behaviour
- TODO: Benchmark comparison for the deep writeupThe _notes.md file is for personal notes and research — it is not published or processed.
A Changeset represents all the changes that shipped in a specific version. It references changes by their IDs rather than duplicating information.
changesets/<org>/<repo>/<version>.yaml
For example:
changesets/laravel/framework/12.11.0.yamlchangesets/laravel/laravel/12.11.2.yaml
# Required
name: laravel-framework-12.11.0
project: laravel/framework
version: 12.11.0
# Recommended
published_at: 2026-01-22
links:
compare: https://github.com/laravel/framework/compare/v12.10.0...v12.11.0
release: https://github.com/laravel/framework/releases/tag/v12.11.0
# Optional
summary: Performance improvements and new Collection methods.
highlights:
- collection-lazy-by-default
- query-builder-upsert-returning
# Change list (order can indicate priority)
changes:
- collection-lazy-by-default
- query-builder-upsert-returning
- middleware-priority-config
- blade-fragment-caching
- artisan-make-enum-command| Field | Type | Required | Description |
|---|---|---|---|
name |
string | ✓ | Unique identifier for this changeset |
project |
string | ✓ | Repository in org/repo format |
version |
string | ✓ | Version string |
published_at |
date | ISO date when the version was released | |
links |
object | Related URLs | |
summary |
string | One-line summary of the release | |
highlights |
array | Change IDs to feature prominently | |
changes |
array | ✓ | All change IDs included in this version |
| Field | Type | Description |
|---|---|---|
compare |
string | GitHub compare URL |
release |
string | GitHub release page URL |
blog |
string | Official blog post URL |
upgrade |
string | Upgrade guide URL |
changesets/laravel/framework/12.11.0.yaml
name: laravel-framework-12.11.0
project: laravel/framework
version: 12.11.0
published_at: 2026-01-22
summary: Performance improvements and new Collection methods.
links:
compare: https://github.com/laravel/framework/compare/v12.10.0...v12.11.0
release: https://github.com/laravel/framework/releases/tag/v12.11.0
highlights:
- collection-lazy-by-default
- query-builder-upsert-returning
changes:
- collection-lazy-by-default
- query-builder-upsert-returning
- middleware-priority-config
- blade-fragment-caching
- artisan-make-enum-command
- validator-sometimes-closure
- http-client-retry-callbackRanges (e.g., "12.10.0 → 12.11.2") should be computed by reading multiple per-version changesets. Only create a dedicated range file if it's editorial/curated content (upgrade guide, cross-repo bundle, weekly digest).
This repository uses custom block syntax (Markdoc-style) for structured content that goes beyond standard Markdown.
{% blockName attr="value" %}
Content
{% /blockName %}Some blocks have required child blocks:
{% parent %}
{% child %}...{% /child %}
{% /parent %}Marks inline code as a semantic reference with optional linking.
This resolves via {% ref type="class" value="Illuminate\\Container\\Container" %}.| Attribute | Required | Description |
|---|---|---|
type |
✓ | Reference type (see below) |
value |
✓ | Canonical identifier |
text |
Display text (defaults to value) |
|
href |
Explicit URL override | |
title |
Tooltip text | |
project |
Repository hint (e.g., laravel/framework) |
|
version |
Version hint for resolution | |
short |
Boolean to use a short version |
Type Values: class, method, function, type, variable, config, env, file, topic, article,
knowledgebase, change, pr, commit, compare, colour, key
Semantic notice box for tips, warnings, etc.
{% callout type="warning" title="Order matters" %}
Middleware priority can override group ordering in surprising ways.
{% /callout %}| Attribute | Required | Description |
|---|---|---|
type |
✓ | note, tip, info, warning, danger |
title |
Heading text | |
icon |
Presentation hint |
A self-closing tag that adds metadata to a standard markdown code fence.
```php {% code filename="bootstrap/app.php" highlight="12-30" /%}
<?php
// ...
```The tag appears on the same line as the opening fence, after the language identifier. The tag is self-closing (/%}).
| Attribute | Required | Description |
|---|---|---|
filename |
Displayed filename/header | |
highlight |
Line ranges (e.g., 3-7 or 3,5,8-12) |
|
focus |
Boolean for "focus mode" presentation | |
class |
The class that code originally sits in | |
method |
The method that the code was taken from | |
start |
The line number start |
Side-by-side content alternatives.
{% tabs %}
{% tab title="Laravel 11" %}
Content for Laravel 11…
{% /tab %}
{% tab title="Laravel 12" %}
Content for Laravel 12…
{% /tab %}
{% /tabs %}tabs attributes:
| Attribute | Description |
|---|---|
style |
Presentation hint (pill, underline) |
tab attributes:
| Attribute | Required | Description |
|---|---|---|
title |
✓ | Tab label |
id |
Stable identifier for deep linking |
Include content from another file.
{% include file="snippets/container-resolution.md" %}| Attribute | Required | Description |
|---|---|---|
file |
✓ | Path relative to repo root |
Render changes for a released version.
{% changeset project="laravel/framework" version="12.11.0" mode="highlights" %}| Attribute | Required | Description |
|---|---|---|
project |
✓ | Repository (org/repo) |
version |
✓ | Version string |
mode |
highlights, full, grouped, upgrade, deep |
Mode Values:
| Mode | Description |
|---|---|
highlights |
Top items from highlights list |
full |
All changes |
grouped |
Grouped by type/scope/topic |
upgrade |
Medium/high impact changes only |
deep |
Includes .deep.md writeups where available |
For ranges:
{% changesetRange project="laravel/framework" from="12.10.0" to="12.11.2" mode="full" %}Render a single change by ID.
{% change id="collection-lazy-by-default" mode="summary" %}| Attribute | Required | Description |
|---|---|---|
id |
✓ | Change ID |
mode |
brief, summary, deep |
Capture corrections without rewriting history.
{% correction title="Model binding timing" date="2026-01-25" %}
{% old %}
I originally said model binding happens *before* middleware.
{% /old %}
{% new %}
Model binding happens *after* the route is matched, during route resolution.
{% /new %}
{% /correction %}correction attributes:
| Attribute | Description |
|---|---|
title |
Short label |
date |
When the correction was made |
reason |
Brief justification |
Child blocks:
| Block | Required | Description |
|---|---|---|
old |
✓ | The incorrect content |
new |
✓ | The corrected content |
- Categories describe format/intent, not subject
- An article has exactly one category
- Choose the category that best describes what the article is
- Topics describe subject matter
- Assign the most specific (leaf) topics
- Parent topics are implicit — don't duplicate them manually
- Content can have multiple topics
- Keep tags flat and minimal
- Use tags for cross-cutting facets:
performance,security,breaking-change,octane,postgres - Don't duplicate information already captured by topics
- IDs should be stable and semantic
- Prefer descriptive IDs over version-based ones for changes
- Slugs should be URL-safe (lowercase, hyphens, no special characters)
- Markdown/YAML in this repo is canonical — all other representations are derived
- Never store derived data — HTML, search indexes, excerpts are generated downstream
- The publishing app consumes this repo — it should not modify it
- Git is the history — don't manually track revision history in files
Single-file format:
- Create
articles/<slug>.md - Add front matter with
title,content(slug, status, category),classification(topics) - Write content
Directory format:
- Create
articles/<slug>/ - Create
article.yamlwith metadata - Create
article.mdwith content - Optionally add
_notes.mdfor personal notes
- Create
series/<slug>.yaml - Define
name,title,content(slug, status, summary) - List article slugs in
articlesarray
- Create
courses/<slug>/ - Create
course.yamlwithname,title,content(slug, status, summary),chapters - Create chapter files (
01-intro.md, etc.) - Optionally add
_notes.mdfor personal notes
- Create
knowledgebase/<slug>.md - Add front matter with
title,content(slug, status, kind),classification(topics) - Add
relationships.relatedlinks to related entries - Write content
- Create
projects/<slug>/ - Create
project.yamlwithtitle,content(slug, status, tagline),lifecycle,links - Create
project.mdwith description/overview - Optionally add
_notes.mdfor personal notes - Optionally set
relationships.project: <slug>on related articles, series, knowledgebase entries, or courses
- Create change entries for new meaningful changes:
- Single-file:
changes/laravel/<repo>/<change-id>.yaml(plus optional.brief.md/.deep.md) - Directory:
changes/laravel/<repo>/<change-id>/withchange.yaml(plus optionalbrief.md/deep.md/_notes.md)
- Single-file:
- Create changeset in
changesets/laravel/<repo>/<version>.yaml - Write release article referencing the changeset via block tags
- Add entry to
categories.yaml - Include name, slug, description
- Add to
topics.yamlin the appropriate hierarchy - Include name and optional description