A cookie is a small piece of text data stored in the user's browser and sent back to the server on later requests. PHP can create cookies, read cookies, update cookies, and delete cookies. Cookies are useful for remembering lightweight information such as theme preference, language choice, cookie consent, referral source, and non-sensitive user preferences.
Cookies are part of HTTP headers. That means PHP must send cookies before it sends any HTML, spaces, new lines, or other output to the browser. If output has already started, setcookie() cannot add the required Set-Cookie header.
Set-Cookie header in the response.Cookie request header.$_COOKIE superglobal.The basic PHP function for creating cookies is setcookie(). A simple cookie needs a name, value, and optional expiry time. The cookie will not appear in $_COOKIE until the next request because $_COOKIE contains cookies received from the browser, not cookies you just scheduled to send.
<?php
// Must run before any HTML output.
// Cookie expires in 1 hour.
setcookie('username', 'Alice', time() + 3600);
// Cookie expires in 30 days.
setcookie('theme', 'dark', time() + (86400 * 30));
echo 'Cookies have been sent. Refresh the page to read them from $_COOKIE.';
?>
PHP stores incoming cookies in $_COOKIE, which is an associative array. Always check whether a cookie exists before reading it, and always escape cookie values before displaying them in HTML. Cookies are user-controlled data, so never trust them blindly.
<?php
$username = $_COOKIE['username'] ?? 'Guest';
$theme = $_COOKIE['theme'] ?? 'light';
echo 'Welcome, ' . htmlspecialchars($username, ENT_QUOTES, 'UTF-8');
echo '<br>';
echo 'Theme: ' . htmlspecialchars($theme, ENT_QUOTES, 'UTF-8');
?>
A cookie is more than just a name and value. Its options decide how long it lasts, where it is sent, and how protected it is.
| Option | Meaning | Example |
|---|---|---|
expires |
Unix timestamp for when the cookie expires. | time() + 86400 |
path |
URL path where the cookie is available. | / for the whole site |
domain |
Domain that can receive the cookie. | example.com |
secure |
Send only over HTTPS. | true |
httponly |
Block JavaScript access to the cookie. | true |
samesite |
Controls cross-site cookie sending. | Lax, Strict, or None |
Modern PHP supports an options array for setcookie(). This is clearer than passing many positional parameters, and it supports SameSite cleanly.
<?php
setcookie('theme', 'dark', [
'expires' => time() + (86400 * 30), // 30 days
'path' => '/',
'domain' => '', // current host
'secure' => true, // HTTPS only
'httponly' => true, // unavailable to JavaScript
'samesite' => 'Lax', // good default for most sites
]);
echo 'Secure cookie sent.';
?>
Use SameSite=Lax for many normal website cookies. Use SameSite=Strict for stricter same-site behavior. Use SameSite=None only when a cookie must be sent in third-party contexts, and pair it with Secure.
A cookie without an expiry time is a session cookie. It is normally removed when the browser session ends. A cookie with an expiry time is a persistent cookie, so the browser can keep it until the expiry date.
<?php
// Session cookie: browser removes it when the browser session ends.
setcookie('temporary_notice', 'shown');
// Persistent cookie: browser keeps it for 90 days.
setcookie('language', 'en', [
'expires' => time() + (86400 * 90),
'path' => '/',
'samesite' => 'Lax',
]);
?>
To update a cookie, call setcookie() again with the same name, path, and domain. The browser replaces the old cookie with the new one. If the path or domain does not match, you may accidentally create a second cookie instead of replacing the first one.
<?php
// Original cookie.
setcookie('theme', 'light', [
'expires' => time() + (86400 * 30),
'path' => '/',
'samesite' => 'Lax',
]);
// Later, update the same cookie.
setcookie('theme', 'dark', [
'expires' => time() + (86400 * 30),
'path' => '/',
'samesite' => 'Lax',
]);
?>
To delete a cookie, set the same cookie name with an expiry time in the past. Use the same path and domain that were used when the cookie was created. You may also unset the value from $_COOKIE so the rest of the current request does not keep using the old value.
<?php
setcookie('theme', '', [
'expires' => time() - 3600,
'path' => '/',
'samesite' => 'Lax',
]);
unset($_COOKIE['theme']);
echo 'Theme cookie deleted.';
?>
A common use of cookies is remembering a user's theme preference. The cookie stores only a safe preference value, not private account data.
<?php
$allowedThemes = ['light', 'dark'];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$theme = $_POST['theme'] ?? 'light';
if (!in_array($theme, $allowedThemes, true)) {
$theme = 'light';
}
setcookie('theme', $theme, [
'expires' => time() + (86400 * 365),
'path' => '/',
'secure' => !empty($_SERVER['HTTPS']),
'httponly' => true,
'samesite' => 'Lax',
]);
$_COOKIE['theme'] = $theme; // Use the new value during this request.
}
$currentTheme = $_COOKIE['theme'] ?? 'light';
if (!in_array($currentTheme, $allowedThemes, true)) {
$currentTheme = 'light';
}
?>
<body class="theme-<?= htmlspecialchars($currentTheme, ENT_QUOTES, 'UTF-8') ?>">
<form method="post">
<select name="theme">
<option value="light" <?= $currentTheme === 'light' ? 'selected' : '' ?>>Light</option>
<option value="dark" <?= $currentTheme === 'dark' ? 'selected' : '' ?>>Dark</option>
</select>
<button type="submit">Save theme</button>
</form>
</body>
Many sites store a small consent cookie after a user accepts a cookie banner. The example below stores only whether the banner has been accepted.
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['cookie_consent'] ?? '') === 'yes') {
setcookie('cookie_consent', 'yes', [
'expires' => time() + (86400 * 180),
'path' => '/',
'secure' => !empty($_SERVER['HTTPS']),
'httponly' => true,
'samesite' => 'Lax',
]);
$_COOKIE['cookie_consent'] = 'yes';
}
$hasConsent = ($_COOKIE['cookie_consent'] ?? '') === 'yes';
?>
<?php if (!$hasConsent): ?>
<form method="post" class="cookie-banner">
<p>We use cookies to remember your preferences.</p>
<input type="hidden" name="cookie_consent" value="yes">
<button type="submit">Accept</button>
</form>
<?php endif; ?>
Cookies are stored on the client, so users can inspect, edit, delete, or fake them. Treat every cookie value as untrusted input. Do not store passwords, raw tokens, credit tl-card numbers, private profile data, or authorization decisions directly in cookies.
| Rule | Why it matters |
|---|---|
Use HttpOnly |
Reduces the chance that JavaScript-based XSS can read the cookie. |
Use Secure |
Sends the cookie only over HTTPS. |
Use SameSite |
Helps control whether cookies are sent with cross-site requests. |
| Validate values | A cookie is user input. Check it before trusting it. |
| Keep values small | Cookies are sent with requests, so large cookies slow down traffic. |
| Use sessions for sensitive state | Sessions store sensitive data server-side and only keep a session ID in the browser. |
If you need to detect whether a user modified a cookie, sign the value with an HMAC. This does not hide the value, but it lets your application reject tampered data. For private data, use server-side sessions or carefully designed encryption instead.
<?php
$secret = 'replace-this-with-a-long-random-secret';
function signCookieValue(string $value, string $secret): string
{
$signature = hash_hmac('sha256', $value, $secret);
return $value . '.' . $signature;
}
function readSignedCookie(string $cookie, string $secret): ?string
{
$parts = explode('.', $cookie, 2);
if (count($parts) !== 2) {
return null;
}
[$value, $signature] = $parts;
$expected = hash_hmac('sha256', $value, $secret);
return hash_equals($expected, $signature) ? $value : null;
}
setcookie('referrer', signCookieValue('newsletter', $secret), [
'expires' => time() + 86400,
'path' => '/',
'secure' => !empty($_SERVER['HTTPS']),
'httponly' => true,
'samesite' => 'Lax',
]);
$referrer = isset($_COOKIE['referrer'])
? readSignedCookie($_COOKIE['referrer'], $secret)
: null;
?>
| Feature | Cookies | Sessions |
|---|---|---|
| Storage location | Browser | Server |
| Best for | Preferences, consent, lightweight non-sensitive values | Login state, cart data, private user state |
| User can edit it? | Yes | Only the session ID is in the browser |
| Size | Small values only | Can hold more data, depending on storage |
| PHP API | setcookie(), $_COOKIE |
session_start(), $_SESSION |
PHP cookies are best for small, non-sensitive values that the browser should remember between requests. Use setcookie() before output to create, update, or delete cookies, and read incoming values from $_COOKIE. Always treat cookie data as untrusted input, escape values before display, and use security flags such as HttpOnly, Secure, and SameSite for safer behavior.
setcookie() sends a cookie through HTTP headers, so it must run before page output.
$_COOKIE contains cookies received from the browser on the current request.
setcookie() is usually readable from $_COOKIE on the next request.
HttpOnly, Secure, and SameSite for safer cookies.
Calling setcookie() after HTML output
Call setcookie() before any output
Trusting $_COOKIE values directly
Validate and escape cookie values before use
Deleting a cookie with a different path
Delete with the same name, path, and domain
setcookie() sends a cookie to the browser for future requests. $_COOKIE contains cookies that arrived with the current request, so the new value normally appears after the next page load.
No. Never store passwords or sensitive account data in cookies. Use server-side sessions for login state and store only a session ID in the browser.
HttpOnly tells the browser not to expose the cookie to JavaScript. This helps reduce the impact of some XSS attacks, but it does not replace output escaping and other security practices.
Lax is a practical default for many website cookies. Strict is more restrictive. None is for third-party cookie use cases and must be combined with Secure.
Explore 500+ free tutorials across 20+ languages and frameworks.