Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
715921e
Initial plan
Copilot Jan 31, 2026
89c89c9
Add complete 5.1→6.0 upgrade guide and remove old upgrade guides
Copilot Jan 31, 2026
4ec6d51
Update Chapter 15 introduction and overview for Vue 3/modern stack
Copilot Jan 31, 2026
0fb9520
Update Chapter 15: exporting-variables and vue-components for modern …
Copilot Jan 31, 2026
dba1f2e
Complete Chapter 15 (Client-Side Code) with Vue 3 components
Copilot Jan 31, 2026
a752acd
Update all remaining chapters from obsolete to wip status
Copilot Jan 31, 2026
67e3b30
Fix all relative image paths to use absolute paths
Copilot Jan 31, 2026
358ed5b
Improve chapter introductions with problem-solution structure
Copilot Jan 31, 2026
bd2f7c9
Improve high-priority docs.md pages with engaging introductions (Batc…
Copilot Jan 31, 2026
dd5f898
Improve docs.md pages with engaging introductions (Batch 2 - final)
Copilot Jan 31, 2026
cd141db
Proofread documentation: fix spelling, grammar, and duplicate words
Copilot Jan 31, 2026
80544c5
Fix path switch up
lcharette Feb 1, 2026
f3a33e8
Fix image paths and internal links to use relative paths instead of a…
Copilot Feb 1, 2026
497853b
Add Alerts instructions
lcharette Feb 1, 2026
3f83ca3
Review of Chapter 2
lcharette Feb 5, 2026
0c198af
Review of Chapter 3
lcharette Feb 6, 2026
9141ba4
Review of Chapter 4
lcharette Feb 6, 2026
084a3b0
Review of Chapter 5
lcharette Feb 6, 2026
855a04a
Review of Chapter 6
lcharette Feb 7, 2026
bb05f18
Review of Chapter 7
lcharette Feb 7, 2026
f61d356
Review of Chapter 8
lcharette Feb 7, 2026
3147786
Misc fixes
lcharette Feb 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 31 additions & 6 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,17 +73,42 @@ All code snippets should use triple backticks with language specified for syntax

### Images and Links

**CRITICAL - Image Paths**: Images are stored in `app/pages/{version}/images` alongside the markdown files, and **MUST always use absolute paths starting with `/`**.
- ✅ Correct: `![Alt text](/images/screenshot.png)`
- ❌ Wrong: `![Alt text](images/screenshot.png)` or `![Alt text](../images/screenshot.png)`
**CRITICAL - Image Paths**: Images are stored in `app/pages/{version}/images/` alongside the markdown files, and **MUST always use a path relative to the _version_ folder, _without_ a starting `/`**.
- ✅ Correct: `![Alt text](images/screenshot.png)`
- ❌ Wrong: `![Alt text](/images/screenshot.png)` or `![Alt text](../images/screenshot.png)`
Images should be checked to ensure the file exists at the specified path.

Internal links should use absolute paths without version numbers to allow automatic version switching.
- ✅ Correct: `[Requirements](/installation/requirements)`
- ❌ Wrong: `[Requirements](installation/requirements)` or `[Requirements](/04.installation/01.requirements)`
Internal links should use relative paths to the version folder, without version numbers to allow automatic version switching.
- ✅ Correct: `[Requirements](installation/requirements)`
- ❌ Wrong: `[Requirements](/installation/requirements)` or `[Requirements](04.installation/01.requirements)`

Pages have anchor links for sections (e.g., `#motivation`). Ensure links point to existing sections.

### Alert Syntax

Alerts can be used to emphasize critical information or provide tips to the user. They are displayed with distinctive colors and icons to indicate the significance of the content.

Use alerts only when they are crucial for user success or to add helpful information. Additionally, you should avoid placing alerts consecutively. Alerts cannot be nested within other elements.

Five types of alerts are available:

```
> [!NOTE]
> Highlights information that users should take into account, even when skimming.

> [!TIP]
> Optional information to help a user be more successful.

> [!IMPORTANT]
> Crucial information necessary for users to succeed.

> [!WARNING]
> Critical content demanding immediate user attention due to potential risks.

> [!CAUTION]
> Negative potential consequences of an action.
```

### Chapters and Pages
Documentation pages are stored in `docs.md` or `chapter.md` files under `app/pages/{version}/` (e.g., `app/pages/6.0/01.quick-start/docs.md`).

Expand Down
2 changes: 1 addition & 1 deletion app/pages/6.0/02.background/01.introduction/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ The PHP community has evolved considerably over the past two decades, beginning

This breakneck pace has caused a lot of people to get left behind. For someone who hasn't been doing web development continuously for the past decade or more, it can feel like an overwhelming task to get acquainted with all of the new tools and frameworks that seem to be coming out every day. Relevant comic from [Abstruse Goose](http://abstrusegoose.com/503):

![BlooP and FlooP and GlooP](/images/theoretical_mathematics_however_never_goes_out_of_fashion.png)
![BlooP and FlooP and GlooP](images/theoretical_mathematics_however_never_goes_out_of_fashion.png)

The problem is that when you're a busy developer with real-world projects to work on, it's very difficult to set aside time to read a book about technology X - especially when you're not even sure that you really _need_ to learn X!

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
---
title: The Client-Server Conversation
description: Many developers do not really understand the basics of how HTTP and web applications work. This discussion attempts to clarify some common misconceptions.
obsolete: true
---

One of the most common misconceptions is that web applications are coherent pieces of software that sit on a server somewhere, and that the client "runs" this application in their browser. This is actually an illusion, carefully crafted to provide a smooth experience for the end user.

In reality, web applications are *conversations* between two agents with very poor memory - the **server**, and the **client** - which may be a web browser, mobile app, or another application. In modern web applications both the client *and* the server are typically going to need to run some code throughout their conversation. What's more, in the case of a PHP application, the client and server don't even speak the same language! The server runs only PHP, while the client runs only Javascript. (Note that there *are* server-side Javascript stacks, but we do not use them.)
In reality, web applications are *conversations* between two agents with very poor memory - the **server**, and the **client** - which may be a web browser, mobile app, or another application. In modern web applications both the client *and* the server are typically going to need to run some code throughout their conversation. What's more, in the case of a PHP application, the client and server don't even speak the same language! The server runs PHP (via UserFrosting's Slim-based backend), while the client runs JavaScript (often with frameworks like Vue). Note that there *are* server-side JavaScript stacks, but UserFrosting is built on PHP.

## Server-side versus client-side code

Expand All @@ -32,60 +31,53 @@ You don't need to do anything. Your web server is configured to do this automati

> How do I get PHP in my Javascript?

Again, clients can't run PHP in their browsers. If you want to pass along the values of some PHP variables to the Javascript you send back to the client, you need to explicitly *generate* Javascript variables using PHP. For example:

```html
<?php

// This is a PHP variable - (note the leading $)
$baseUrl = "https://owlfancy.com";

?>
Again, clients can't run PHP in their browsers. If you want to pass along the values of some PHP variables to the JavaScript you send back to the client, you need to explicitly *generate* JavaScript variables using your templating engine. UserFrosting uses **Twig** for this. For example:

```twig
<button id="updateButton" type="button">Update My Owl</button>

<script>
// This is a Javascript variable - (note no leading $)
// JavaScript variable populated by PHP
let baseUrl = <?php echo $baseUrl; ?> ;

// Our Javascript code can now reference the Javascript variable baseUrl
$("#updateButton").on("click", function () {
$("#myOwlLink").prop("attr", baseUrl + "/great-horned-owl");
// Our JavaScript code can now reference the baseUrl variable
document.getElementById('updateButton').addEventListener('click', () => {
document.getElementById('myOwlLink').href = `${baseUrl}/great-horned-owl`;
});
</script>
```

UserFrosting has a cleaner way of doing this using the Twig templating engine, but the principle is still the same.
This uses PHP syntax to inject server-side values into the client-side JavaScript. It can also be done using Twig's `{{ }}` syntax. The templating happens server-side before the HTML is sent to the browser.

For more complex PHP code that needs to be run in the middle of a block of Javascript code (for example, querying the database, which can *only* be done server-side), we'll need a way to let Javascript code ask the server to run some code on its behalf. Remember, the only way we can run code on the server is by making requests and then waiting for a response!

Fortunately, modern browsers support something called AJAX, which allows Javascript code to automatically make requests to the server. Thus, you might see something like:

```php
<?php
$baseUrl = "https://owlfancy.com";
$owlId = getUserOwlId();
?>
Fortunately, modern browsers support something called AJAX (Asynchronous JavaScript and XML), which allows JavaScript code to make requests to the server without reloading the page. Applications typically use the modern `fetch()` API for this. For example:

```twig
<button id="seeVoleHuntResults" type="button">See Vole Hunt Results</button>

<script>
let baseUrl = "<?php echo $baseUrl; ?>";
let owlId = "<?php echo $owlId; ?>";

$("#seeVoleHuntResults").on("click", function () {
// This is a jQuery AJAX function that generates another request to the server!
$.getJSON(baseUrl + "/vole-hunt/today",
{
"owl_id": owlId
}).done(function(data) {
// This is what the browser does when the request is complete successfuly.
alert(data['voles_caught']);
});
document.getElementById('seeVoleHuntResults').addEventListener('click', async () => {
try {
// Modern fetch API generates an asynchronous request to the server
const response = await fetch(`${baseUrl}/api/vole-hunt/today?owl_id=${owlId}`);
const data = await response.json();

// Display the result when the request completes successfully
alert(`Voles caught: ${data.voles_caught}`);
} catch (error) {
console.error('Failed to fetch vole hunt results:', error);
}
});
</script>
```

> [!NOTE]
> For more complex interactions, UserFrosting supports **Vue 3** components, which provide a more structured way to build reactive user interfaces. Vue components handle state management, reactive data binding, and component lifecycle automatically, making it easier to build sophisticated client-side features. You'll learn more about Vue integration in the [Asset Management](/asset-management) chapter.

### The Big Picture

Let's take a step back and talk about what's really going on here.
Expand All @@ -94,9 +86,9 @@ Let's take a step back and talk about what's really going on here.

*requests a dynamically generated resource from the server...*

*which contains some HTML and Javascript...*
*which contains some HTML, JavaScript, CSS, and references to other assets...*

*that Javascript contains instructions for the user's browser...*
*that JavaScript contains instructions for the user's browser...*

*one of which is to automatically ask the server for some JSON data whenever the user presses a certain button...*

Expand All @@ -114,13 +106,13 @@ It might be easier to understand this whole process if we provide an example of

**owlfancy.com:** "Sure. Looks like for that resource, I'm supposed to run this bit of code over here. Let's see what happens when I do that...Ok, it's done! Looks like it returned some HTML. Here you go. The status code is 200. Let me know if you need anything else. Bye!"

**Your browser:** "Hmm, according to this, I'm supposed to ask you for *jquery.js*, *bootstrap.js*, *pellet.js*, *bootstrap.css*, and something called */images/preening.jpg*. Can I have those as well please? I'm also supposed to get *https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic*, but that's not your problem. I can ask *fonts.googleapis.com* myself.
**Your browser:** "Hmm, according to this, I'm supposed to ask you for some JavaScript bundles (*app.js*, *vendor.js*), some CSS (*theme.css*), and something called */images/preening.jpg*. Can I have those as well please? I'm also supposed to get *https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic*, but that's not your problem. I can ask *fonts.googleapis.com* myself.

**owlfancy.com: (for each requested item):** "Sure, here you go. Let me know if you need anything else. Bye!"

**Your browser:** "Ok, now I'll run all this CSS and Javascript code." *Runs jquery.js, bootstrap.js, and pellet.js*
**Your browser:** "Ok, now I'll run all this CSS and JavaScript code." *Runs app.js and vendor.js*

**Your browser:** "Hmm, looks like I'm supposed to give these boxes over here a border and shadow, change the font for this to Lora, and set it up so that when someone clicks "Forage", a picture of an owl swoops across the screen. Oh, it also wants me to tell Google Analytics and Facebook what I just did. Sure, after all my owner didn't tell me *not* to do that...I'll just send that information right now.
**Your browser:** "Hmm, looks like I'm supposed to give these boxes over here a border and shadow, change the font for this to Lora, mount some Vue components, and set it up so that when someone clicks "Forage", a picture of an owl swoops across the screen. Oh, it also wants me to tell Google Analytics and Facebook what I just did. Sure, after all my owner didn't tell me *not* to do that...I'll just send that information right now.

**Your browser:** "La la, waiting for my user to do something..."

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
---
title: Develop Locally, Serve Globally
description: The right way to approach development.
obsolete: true
---

Just about every week, we see someone wander into [chat](https://chat.userfrosting.com) and ask:
Expand All @@ -28,9 +27,9 @@ In this same vein, any framework or CMS that has you do a "one-click install" is

## Setting up a local development environment

If you think that setting up a local environment is too much work, think again! On a MacOS or Linux computer, setting up a local environment simply consist of installing a couple of apps through the command line. On a Windows 10 or 11 machine, an additional step is required : Installing the *Windows Subsystem for Linux (WSL2)*!
If you think that setting up a local environment is too much work, think again! On a MacOS or Linux computer, setting up a local environment simply consist of installing a couple of apps through the command line. On a Windows 10 or 11 machine, an additional step is required : Installing the *Windows Subsystem for Linux (WSL2)*!

And the sprinkle on the cupcake is the [next chapter](installation) will teach you how to do everything yourself!
And the sprinkle on the cupcake is the [Installation chapter](/installation) will teach you how to do everything yourself!

> [!WARNING]
> There are a number of "one-click" installers available, which can set up your machine with a complete web application stack in just a few minutes: **XAMPP**, **MAMP**, **WampServer**, etc. **These are not officially supported by UserFrosting and we do not recommend using them.** They can be slow, out of date or use obscure configuration. They were useful at some point, but with modern tools, especially with WSL2 on Windows, it's never been easier to install every tool you need locally If you insist on using a "one-click" solution, [Docker](#alternatives-to-installing-a-stack-natively-docker) is a [great, modern alternative](https://www.reddit.com/r/PHP/comments/gqhg15/comment/frt8cp0/).
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
---
title: Don't Reinvent the Wheel
description: Using third-party components reduces the amount of software maintenance you have to do and documentation you have to write, and lets you draw on the wider community of other developers who use those packages for troubleshooting and support.
obsolete: true
---

I think that for a lot of developers - novices and professionals alike - building on top of others' work can seem like a betrayal of our trade. We're not "real" developers unless we built everything with our bare hands from scratch, and know firsthand the nitty-gritty details of how our code works. With third-party components, we have to take time to actually *learn* how to use them, and follow *their* rules. I get it. It all feels so antithetical to the DIY spirit that got so many of us into coding in the first place. Trust me, as someone who built a cold frame out of some doors and framing I found in the dumpster, I know:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
---
title: Server Misconfiguration
description: Server misconfiguration is one of the top 10 vulnerabilities of any web application, according to OWASP. Most of these misconfigurations occur because of inexperienced developers or system administrators, and are simple to fix.
obsolete: true
---

As we discussed in [The Client-Server Conversation](background/the-client-server-conversation), it's important to distinguish between the code that is running on the server, and the response that it sends back to the client.
Expand Down
48 changes: 48 additions & 0 deletions app/pages/6.0/02.background/05.security/02.csrf-protection/docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
title: CSRF Protection
description: Cross-Site Request Forgery (CSRF) attacks trick authenticated users into executing unwanted actions. UserFrosting provides built-in CSRF protection to prevent these attacks.
---

## What is CSRF?

Cross-Site Request Forgery (CSRF) is an attack that forces an authenticated user to execute unwanted actions on a web application. If a user is authenticated to your site, an attacker can trick their browser into making requests to your application on their behalf.

For example, imagine a user is logged into your banking application. They then visit a malicious website that contains this hidden form:

```html
<form action="https://yourbank.com/transfer" method="POST" id="evil">
<input type="hidden" name="to" value="attacker-account">
<input type="hidden" name="amount" value="10000">
</form>
<script>document.getElementById('evil').submit();</script>
```

Because the user is already authenticated (their browser has the session cookie), this request would succeed unless you have CSRF protection in place.

## How UserFrosting Protects Against CSRF

UserFrosting uses **CSRF tokens** to protect against these attacks. A CSRF token is a unique, secret value generated by the server and tied to the user's session. When a form is submitted or an AJAX request is made, the token must be included in the request. The server validates that the token matches what it expects for that session.

We'll see in [a later chapter](/routes-and-controllers/client-input/csrf-guard) how to include CSRF tokens in your forms and AJAX requests.

## When CSRF Protection Applies ?

CSRF protection is relevant to:
- **POST** requests
- **PUT** requests
- **DELETE** requests
- **PATCH** requests

**GET** requests are not protected, as they shouldn't modify state anyway.

If a CSRF token is missing or invalid, the system should reject the request with a 400 Bad Request error. This protects your application but means you need to ensure:

1. Every form includes the CSRF token
2. AJAX requests include the token in the payload
3. Tokens are refreshed if sessions expire
4. Single-page applications properly manage token lifecycle

> [!WARNING]
> Never disable CSRF protection in production. If you're having issues with CSRF validation, fix the root cause rather than disabling this critical security feature.

For stateless APIs that use token-based authentication (like OAuth or JWT), CSRF protection is typically not needed because there are no cookies involved. However, if your API endpoints rely on session cookies for authentication, CSRF protection is essential.
Loading
Loading