Cookies
A cookie is a small named value the browser stores and sends back with each request, and it is data the client can change.
setcookie(string $name, string $value = "", array $options = []): boolHTTP has no memory. Each request arrives on its own, and nothing in it tells the server that the same person sent the previous one. A cookie is the usual fix. It is a small named string that your response asks the browser to store and send back on every later request to your site.
Writing and reading
setcookie() does not reach into the browser. It adds a Set-Cookie header to the response, so it has to run before anything is printed. One blank line above <?php counts as output and the call fails.
Cookies the browser sent arrive in $_COOKIE, an array of names and values. They are always strings.
The options
Since PHP 7.3 the third argument may be an options array. expires is a Unix timestamp, and 0 means the cookie dies when the browser closes. path and domain limit where it is sent. secure sends it only over HTTPS. httponly hides it from JavaScript. samesite decides whether it travels on requests started by other sites; Lax is a sensible default. PHP 8.5 also accepts partitioned. An unrecognised key throws a ValueError.
What trips people up
The cookie is stored on the client, so the person holding it can change it. A cookie that says role=admin is a claim, not a fact. Keep values whose meaning lives on the server, such as a session id, or sign the value with hash_hmac() and check the signature when it comes back.
Example
When to use it
- Remember a visitor's chosen theme without making them sign in.
- Keep a cart id for someone who has not created an account yet.
- Store a locale so the next page loads in the same language.
- Set a signed remember-me cookie that survives the browser closing.
More examples
Setting a cookie, both forms
The same cookie written twice: once with the options array and once with the positional arguments it replaced. They are the same call, and you will meet both in other people's code. The positional form cannot express samesite, so reach for the array whenever a cookie matters.
<?php
declare(strict_types=1);
$thirtyDays = time() + 60 * 60 * 24 * 30;
// Options array, available since PHP 7.3. This is the form to use.
setcookie('theme', 'dark', [
'expires' => $thirtyDays,
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax',
]);
// The older positional form does the same thing, in this order:
// name, value, expires, path, domain, secure, httponly.
// It has no slot for samesite, which is why the array form exists.
setcookie('theme', 'dark', $thirtyDays, '/', '', true, true);Deleting a cookie
There is no delete function. You overwrite the cookie with an empty value and a past expiry, and the browser drops it. Reach for this when a preference is reset or a sign-out has to clear a remembered value. If the path does not match the original, the browser keeps the old cookie and you end up with two.
<?php
declare(strict_types=1);
// Deleting a cookie means sending it back with an expiry in the past.
// The path and domain must match the ones it was set with.
setcookie('theme', '', [
'expires' => time() - 3600,
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax',
]);
// $_COOKIE still holds the old value for the rest of this request.
unset($_COOKIE['theme']);A cookie you can trust: sign it
When a cookie really must carry meaning, attach a signature made with a secret only the server knows. Editing the value breaks the signature, and hash_equals reports the mismatch without leaking timing. Use this for a remembered-login token or a plan flag; the secret comes from the environment, never from the code.
<?php
declare(strict_types=1);
$secret = (string) getenv('APP_COOKIE_SECRET');
function signCookie(string $value, string $secret): string
{
return $value . '.' . hash_hmac('sha256', $value, $secret);
}
function readSignedCookie(string $raw, string $secret): ?string
{
$parts = explode('.', $raw, 2);
if (count($parts) !== 2) {
return null;
}
[$value, $mac] = $parts;
return hash_equals(hash_hmac('sha256', $value, $secret), $mac) ? $value : null;
}
setcookie('plan', signCookie('pro', $secret), [
'expires' => 0,
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax',
]);
$plan = readSignedCookie($_COOKIE['plan'] ?? '', $secret) ?? 'free';
Discussion