$_COOKIE

Yo Yogesh.M Updated 01 Nov 2026
7 min read ·Lesson 219 of 219

What Are Cookies?

In simple terms: A cookie is a small piece of text that a website asks the browser to store. On every later request to that website, the browser sends the cookie back. This lets a site "remember" things between page visits — a preferred language, a theme, or which user is logged in. Technically: A server sets a cookie with a Set-Cookie HTTP response header. The browser stores it and includes it in the Cookie request header of subsequent matching requests. PHP parses that header into the $_COOKIE superglobal. Cookies are set with setcookie().

Why Cookies Exist

HTTP is stateless: each request is independent, and the server does not automatically know that two requests came from the same visitor. Cookies add a small amount of state on the client side. Sessions (see $_SESSION) build on cookies: the browser stores only a session ID cookie, and the actual data stays on the server.


Request 1:  Browser ──GET /settings.php──────────────────────────► Server
            Browser ◄──Set-Cookie: theme=dark; Max-Age=31536000── Server   (setcookie)
Request 2:  Browser ──GET /index.php  Cookie: theme=dark─────────► Server
                                       $_COOKIE['theme'] === 'dark'

Important: A cookie set with setcookie() is not available in $_COOKIE during the same request. $_COOKIE reflects what the browser sent with the current request; the new cookie arrives with the next request.


Syntax (PHP 7.3+)

setcookie(string $name, string $value = "", array $options = []): bool
OptionMeaningRecommended
expiresUnix timestamp when the cookie expires; 0 = session cookie (deleted when the browser closes)Set explicitly
pathURL path where the cookie is sent'/'
domainDomain (and subdomains) where the cookie is sentUsually leave empty (current host only)
secureSend only over HTTPStrue in production
httponlyHide from JavaScript (document.cookie)true unless JS must read it
samesiteLax, Strict, or None — controls sending with cross-site requests'Lax'

Example

<?php
// settings.php — must run before any output is sent
setcookie('theme', 'dark', [
    'expires' => time() + 60 * 60 * 24 * 365,   // one year
    'path' => '/',
    'secure' => true,
    'httponly' => true,
    'samesite' => 'Lax',
]);
echo "Theme saved. It will be applied on your next page view.";

Response Header Sent to the Browser (date depends on the current time)

Set-Cookie: theme=dark; expires=Mon, 11 Oct 2027 18:00:00 GMT; Max-Age=31536000; path=/; secure; HttpOnly; SameSite=Lax

Why These Options

  • secure prevents the cookie from travelling over unencrypted HTTP, where it could be intercepted. (On http://localhost during development, browsers may ignore secure cookies — set it based on the environment.)
  • httponly stops JavaScript from reading the cookie, which limits the damage of an XSS vulnerability.
  • samesite: Lax prevents the cookie from being sent with most cross-site requests (such as form posts from other sites), which helps against CSRF. Strict is stricter; None (which requires secure) is needed only for genuinely cross-site use, such as embedded widgets.

Reading Cookies

<?php
// Request header: Cookie: theme=dark; lang=ta
$allowedThemes = ['light', 'dark'];
$theme = $_COOKIE['theme'] ?? 'light';
if (!in_array($theme, $allowedThemes, true)) {
    $theme = 'light';
}
$language = $_COOKIE['lang'] ?? 'en';
echo "Theme: $theme, language: $language\n";

Output

Theme: dark, language: ta

Cookies are user input: the user (or an attacker) can edit, delete, or invent any cookie. Read them with defaults and validate them exactly like $_GET and $_POST data.


Headers Must Be Sent Before Output

Cookies travel in HTTP headers, and headers are sent before the page content. Once any output has been sent — even a space or a blank line before <?php — it is too late:

<html>
<?php
setcookie('theme', 'dark');

Result

PHP Warning:  Cannot modify header information - headers already sent by (output started at /path/settings.php:1) in /path/settings.php on line 3

Fix: Call setcookie() before printing anything — typically at the top of the script or in a controller, before templates are rendered. The warning message tells you exactly where output started. A common hidden cause is whitespace after a closing ?> in an included file, which is why pure PHP files should omit the closing tag.


There is no "delete" function. Instead, set the same cookie again with an expiry date in the past:

<?php
setcookie('theme', '', [
    'expires' => time() - 3600,
    'path' => '/',
    'secure' => true,
    'httponly' => true,
    'samesite' => 'Lax',
]);
unset($_COOKIE['theme']);   // also remove it from the current request's data

The path and domain must match the values used when the cookie was set; otherwise the browser treats it as a different cookie and keeps the original.


Storing Structured Data

Cookie values are strings. To store a small list or settings object, encode it as JSON:

<?php
function addRecentlyViewed(array $ids, int $productId, int $max = 5): array
{
    $ids = array_values(array_unique([$productId, ...$ids]));
    return array_slice($ids, 0, $max);
}
$recent = [];
foreach ([12, 7, 12, 3, 9, 4, 15] as $viewedProduct) {
    $recent = addRecentlyViewed($recent, $viewedProduct);
}
echo json_encode($recent) . "\n";
// In the real page:
// setcookie('recent', json_encode($recent), ['expires' => time() + 2592000, 'path' => '/', 'httponly' => true, 'samesite' => 'Lax']);

Output

[15,4,9,3,12]

How It Works

  1. Each viewed product is placed at the front of the list; array_unique() removes an earlier occurrence, so viewing product 12 again moves it to the front instead of duplicating it.
  2. array_slice() keeps only the five most recent IDs, so the cookie stays small. Product 7 dropped off the end.
  3. The list is stored as JSON in the cookie.

Reading It Back Safely

<?php
// Request header: Cookie: recent=%5B15%2C4%2C9%2C3%2C12%5D
$decoded = json_decode($_COOKIE['recent'] ?? '[]', true);
$recent = is_array($decoded)
    ? array_values(array_filter($decoded, fn($id): bool => is_int($id) && $id > 0))
    : [];
echo implode(', ', $recent) . "\n";

Output

15, 4, 9, 3, 12

setcookie() URL-encodes the value, and PHP decodes it automatically when filling $_COOKIE. Because the cookie might have been edited, the decoded data is checked: it must be an array of positive integers. Never pass cookie contents to unserialize() — it can be exploited to create arbitrary objects.


Protecting Cookies From Tampering

Users can change cookie values freely. If a cookie's value must not be altered, sign it with a secret key using HMAC, and verify the signature when reading:

<?php
const COOKIE_SECRET = 'replace-with-a-long-random-secret-from-configuration';
function signValue(string $value): string
{
    return $value . '.' . hash_hmac('sha256', $value, COOKIE_SECRET);
}
function verifySignedValue(string $signed): ?string
{
    $position = strrpos($signed, '.');
    if ($position === false) {
        return null;
    }
    $value = substr($signed, 0, $position);
    $signature = substr($signed, $position + 1);
    return hash_equals(hash_hmac('sha256', $value, COOKIE_SECRET), $signature) ? $value : null;
}
$cookie = signValue('dark');
var_dump(verifySignedValue($cookie));
var_dump(verifySignedValue('light.' . substr($cookie, 5)));
var_dump(verifySignedValue('dark'));

Output

string(4) "dark"
NULL
NULL

How It Works

  1. hash_hmac() computes a signature from the value and a secret key that only the server knows.
  2. The cookie stores value.signature.
  3. When reading, the server recomputes the signature for the value. If the user changed the value (here, dark to light) without knowing the secret, the signatures no longer match.
  4. hash_equals() compares the signatures in constant time, which prevents timing attacks. Signing prevents modification, but the value is still readable. Sensitive data does not belong in cookies at all — store it in the session or database.

RuleDetail
SizeAbout 4 KB per cookie (name + value + attributes)
NumberBrowsers limit the number of cookies per domain (commonly 50 or more)
Sent with every requestLarge cookies slow down every request, including images and CSS on the same domain
__Host- name prefixBrowsers accept the cookie only if it is secure, has path=/, and has no domain — a strong protection for important cookies
Privacy lawsNon-essential cookies (analytics, advertising) may require user consent, depending on the jurisdiction

Common Mistakes

Effect: $_COOKIE['theme'] is not set right after setcookie(). Fix: Use the value you just set directly, or redirect so the next request carries the cookie.

Calling setcookie() After Output

Effect: "Headers already sent" warning, and the cookie is not set. Fix: Set cookies before any output; omit closing ?> in pure PHP files.

Storing Sensitive Data in Cookies

Effect: User IDs, roles, or prices can be read or changed by the user. Fix: Store an opaque session ID only; keep data on the server.

Forgetting Security Options

Effect: Cookies readable by JavaScript or sent over HTTP. Fix: Use secure, httponly, and samesite.

Deleting With Different path/domain

Effect: The cookie is not removed. Fix: Use the same attributes as when setting it.


Best Practices

  • Use the options array with secure, httponly, and samesite for every cookie.
  • Treat $_COOKIE as untrusted input: use defaults, allow-lists, and type checks.
  • Keep cookies small; store complex data server-side.
  • Sign cookies whose values must not be modified, and never put secrets in them.
  • Set cookies before any output, and remember they appear in $_COOKIE only on the next request. Official reference: https://www.php.net/manual/en/reserved.variables.cookies.php

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else User-defined Functions Slicing Strings Function Arguments PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators AJAX PHP AJAX Database AJAX XML AJAX Live Search AJAX Poll AJAX with JSON Security Introduction Password Hashing SQL Injection Prevention XSS Prevention Garbage Collection Project: Simple Blog PHP What is OOP PHP Classes/Objects The $this Keyword Access Array Items Update Array Items Add Array Items Remove Array Items Sorting Arrays Multidimensional Arrays Array Destructuring Array Spread Operator Search in Arrays Merge Arrays PHP Abstract Classes PHP Interfaces Polymorphism PHP Traits PHP Static Methods PHP Static Properties Late Static Binding Magic Methods Object Cloning MySQL Database MySQL Connect MySQL Create DB MySQL Create Table MySQL Insert Data MySQL Get Last ID MySQL Insert Multiple MySQL Prepared MySQL Select Data MySQL Where Debugging PHP Project: Image Upload Gallery $_SESSION $_ENV PHP RegEx RegEx Patterns Heredoc and Nowdoc RegEx Modifiers preg_match preg_replace preg_split PHP RegEx Functions Nullable Types Mixed and Never Types Nullsafe Operator PHP Attributes PHP Fibers Readonly Classes Property Hooks (PHP 8.4) Asymmetric Visibility (PHP 8.4) Reflection API SPL Data Structures Composer PHP Form Handling PHP XML Parsers PDO Transactions PDO Error Handling PHP XML Parsers PHP SimpleXML Parser PHP SimpleXML - Get PHP XML Expat Parser PHP DOM Parser PHP XMLReader PHP XMLWriter AJAX Intro PHP Class Constants Encryption with Sodium Recursive Functions Variable Functions Anonymous Functions Closures Arrow Functions First-class Callable Syntax PHP Arrays Indexed Arrays Associative Arrays Create Arrays Variables Scope Static Variables Variable Variables PHP Echo / Print PHP printf / sprintf PHP Data Types PHP Booleans PHP NULL PHP Strings String Functions array_map / array_filter / array_reduce Array Functions What Are Superglobals? $GLOBALS What Is `$_SERVER` ? $_REQUEST $_POST $_GET $_FILES $_COOKIE
Ask about this post
AI Ask about this post

Ask questions about $_COOKIE and get answers drawn from it.

Signed-in readers only.