Isomorphic and bulletproof πͺ cookie management for Meteor applications with support for Client, Server, Browser, Cordova, Meteor-Desktop, and other Meteor environments.
- π¨βπ» Stable codebase
- π 320.000+ downloads
- π¨βπ¬ 99.9% tests coverage / TDD
- π¦ No external dependencies (no
underscore
,jQuery
, orBlaze
) - π₯ Consistent API across Server and Client environments
- π± Compatible with Cordova, Browser, Meteor-Desktop, and other client platforms
- γοΈ Full Unicode support for cookie values
- π¨βπ» Supports
String
,Array
,Object
, andBoolean
as cookie value types - βΏ IE support, thanks to @derwok
- π¦ Shipped with TypeScript types
- π¦ Looking for persistent Client (Browser) storage? Try the
ClientStorage
package.
- Installation
- Import
- FAQ
- API
new Cookies()
constructor β Create a newCookies
instance.get()
β Read a cookie.set()
β Set a cookie.remove()
β Remove one or all cookies.keys()
β List all cookie keys.send()
β Sync cookies with the server.sendAsync()
β Sync cookies asynchronously.middleware()
β Register cookie middleware manuallynew CookieCore()
constructor β Low-level class that can be used to directly parse and manage cookies
- Examples
- Running Tests
- Support Our Open Source Contributions
meteor add ostrio:cookies
import { Cookies } from 'meteor/ostrio:cookies';
- Cordova Usage: This recommendation applies only to outgoing cookies from Client β Server. Cookies set by the server work out-of-the-box on the client:
- Enable withCredentials
- Set
{ allowQueryStringCookies: true }
and{ allowedCordovaOrigins: true }
on both Client and Server - When
allowQueryStringCookies
is enabled, cookies are transferred to the server via a query string (GET parameters) - For security, this is allowed only when the
Origin
header matches the regular expression^http://localhost:12[0-9]{3}$
(Meteor/Cordova connects throughlocalhost:12XXX
)
- Cookies Missing on Server? In most cases, this is due to Meteor's HTTP callback-chain ordering. Ensure that
new Cookies()
is called before routes are registered: - Meteor-Desktop Compatibility:
ostrio:cookies
can be used inmeteor-desktop
projects. Since Meteor-Desktop works similarly to Cordova, all Cordova recommendations from above apply
Note
On the server, cookies are set only after headers are sent (i.e. on the next route or page reload)
To sync cookies from Client to Server without a page reload, use sendAsync()
or send()
Tip
On the Server: cookies are implemented as middleware that attaches a CookiesCore
instance to the incoming request (accessible as req.Cookies
). Ensure that the Cookies middleware is registered before other middleware and routes
In .meteor/packages
: Place the ostrio:cookies
package above all community packages, order of packages does matter in this file
See FAQ for more tips
Important
On the Server: it's possible to create many new Cookies()
instances with handler
callbacks and onCookies
hooks, then later each instance can get destroyed calling .destroy()
method.
Note: Only one middleware will be registered and passed into WebApp.connectHandlers.use()
at the time! All consequent handler
and onCookies
callbacks and hooks will be added to shared Map and called as expected within the first registered middleware. Invoking .middleware()
method manually will result in warning and will return "blank" middleware handler which will instantly call NextFunc()
Create a new instance of Cookies
(available on both Client and Server).
Arguments:
opts
{CookiesOptions} - Config object
Available CookiesOptions:
opts.auto
{boolean} β [Server] Auto-bind asreq.Cookies
(default:true
)opts.handler
{function} β [Server] Custom middleware handler; receives theCookies
instanceopts.onCookies
{function} β [Server] Callback triggered after.send()
or.sendAsync()
is called and the cookies are received by the server. (Note: available only ifauto
istrue
.)opts.TTL
{number | boolean} β Default expiration time (max-age) in milliseconds. Set tofalse
for session cookiesopts.runOnServer
{boolean} β Set tofalse
to disable server usage (default:true
)opts.allowQueryStringCookies
{boolean} β Allow passing cookies via query string (primarily for Cordova)opts.allowedCordovaOrigins
{RegExp | boolean} β [Server] Allow setting cookies from specific origins (defaults to^http:\/\/localhost:12[0-9]{3}$
iftrue
)opts.name
{string} - Sets.NAME
property of Cookies & CookiesCore instances, use it for instance identification, defaultCOOKIES
Example:
import { Cookies } from 'meteor/ostrio:cookies';
const cookies = new Cookies({
TTL: 31557600000 // One year TTL
});
(Anywhere) Read a cookie. Returns undefined
if the cookie is not found
Arguments:
key
{string} β The name of the cookie.
cookies.get('age'); // undefined if not found
cookies.set('age', 25); // returns true
cookies.get('age'); // returns 25
(Anywhere) Create or update a cookie
Arguments:
key
{string} β The cookie namevalue
{string | number | boolean | object | array} β The cookie valueopts
{CookieOptions} β Optional settings
Supported CookieOptions:
opts.expires
{number | Date | Infinity}: Cookie expirationopts.maxAge
{number}: Maximum age in secondsopts.path
{string}: Cookie path (default: current path)opts.domain
{string}: Cookie domainopts.secure
{boolean}: Transmit only over HTTPSopts.httpOnly
{boolean}: Inaccessible to client-side JavaScriptopts.sameSite
{boolean | 'None' | 'Strict' | 'Lax'}: Cross-site cookie policyopts.partitioned
{boolean}: SpecifiesPartitioned
attribute inSet-Cookie
header. When enabled, clients will only send the cookie back when the current domain and top-level domain matchesopts.priority
{'Low' | 'Medium' | 'High'}: Specifies the value for thePriority
attribute in `Set-Cookie`` headeropts.firstPartyOnly
{boolean}: Deprecated (usesameSite
instead)
cookies.set('age', 25, {
path: '/',
secure: true
});
(Anywhere) Remove cookie(s)
remove()
β Removes all cookies on the current domainremove(key)
β Removes the specified cookieremove(key, path, domain)
β Removes a cookie with the given key, path, and domain
Arguments:
key
{string} - The name of the cookie to create/overwritepath
{string} - [Optional] The path from where the cookie was readable. E.g., "/", "/mydir"; if not specified, defaults to the current path of the current document location (string or null). The path must be absolute (see RFC 2965). For more information on how to use relative paths in this argument, read moredomain
{string} - [Optional] The domain from where the cookie was readable. E.g., "example.com", ".example.com" (includes all subdomains) or "subdomain.example.com"; if not specified, defaults to the host portion of the current document location (string or null)
const isRemoved = cookies.remove(key, path, domain); // boolean
const isRemoved = cookies.remove('age', '/'); // boolean
const isRemoved = cookies.remove(key, '/', 'example.com'); // boolean
(Anywhere) Check if a cookie exists
Arguments:
key
{string} β The name of the cookie
const hasKey = cookies.has(key); // boolean
const hasKey = cookies.has('age'); // boolean
(Anywhere) Returns an array of all cookie names
const cookieKeys = cookies.keys(); // string[] (e.g., ['locale', 'country', 'gender'])
(Client only) Synchronously send all current cookies to the server via XHR
Arguments:
callback
{function} β Callback with signature(error, response)
.
cookies.send((error, response) => {
if (error) {
console.error(error);
} else {
console.log('Cookies synced:', response);
}
});
(Client only) Asynchronously send all current cookies to the server via XHR
const response = await cookies.sendAsync();
console.log('Cookies synced:', response);
(Server only) Returns a middleware function to integrate cookies into your serverβs request pipeline.
Usage: Register this middleware with your Meteor server (e.g., via WebApp.connectHandlers.use
).
import { WebApp } from 'meteor/webapp';
import { Cookies } from 'meteor/ostrio:cookies';
const cookies = new Cookies({
auto: false,
handler(cookiesInstance) {
// Custom processing with cookiesInstance (of type Cookies)
}
});
WebApp.connectHandlers.use(cookies.middleware());
(Server only) Unregisters hooks, callbacks, and middleware
cookies.isDestroyed // false
cookies.destroy(); // true
cookies.isDestroyed // true
cookies.destroy(); // false β returns `false` as instance was already destroyed
CookiesCore
is low-level constructor that can be used to directly parse and manage cookies
Arguments:
opts
{CookiesCoreOptions} β Optional settings
Supported CookiesCoreOptions:
_cookies
{string | CookieDict} - Cookies string fromdocument.cookie
,Set-Cookie
header, or{ [key: string]: unknown }
ObjectsetCookie
{boolean} - Set totrue
when_cookies
option derivative ofSet-Cookie
headerresponse
{ServerResponse} - HTTP server response objectTTL
{number | false} - Default cookies expiration time (max-age) in milliseconds. If false, the cookie lasts for the sessionrunOnServer
{boolean} - Client only. Iftrue
β enablessend
andsendAsync
from clientallowQueryStringCookies
{boolean} - If true, allow passing cookies via query string (used primarily in Cordova)allowedCordovaOrigins
{RegExp | boolean} - A regular expression or boolean to allow cookies from specific originsopts.name
{string} - Sets.NAME
property of CookiesCore instances, use it for instance identification, defaultCOOKIES_CORE
Note
CookiesCore
instance has the same methods as Cookies
class except .destroy()
and .middleware()
import { CookiesCore } from 'meteor/ostrio:cookies';
if (Meteor.isServer) {
// EXAMPLE SERVER USAGE
WebApp.connectHandlers.use((request, response, next) => {
const headerCookies = response.headers.get('set-cookie');
const cookies = new CookiesCore({
_cookies: headerCookies,
setCookie: true, // <- Switch cookie-parser to header mode
response: response,
});
// FOR EXAMPLE: CHECK SESSION EXPIRATION
if (cookies.has('session-exp')) {
if (cookies.get('session-exp') < Date.now()) {
// .remove() WILL ADD `Set-Cookie` HEADER WITH expires=0 OPTION
cookies.remove('session-id');
cookies.remove('session-exp');
}
} else {
// MARK USER AS NEW
cookies.set('session-type', 'new-user');
}
});
}
if (Meteor.isClient) {
const cookies = new CookiesCore({
// {runOnServer: true} Enables syncing cookies between client and server
// Requires `new Cookies({auto: true})` on server
runOnServer: true,
_cookies: { // <- Set default cookies
key: 'name',
theme: 'dark',
isNew: true,
'agreed-with-gdpr': false,
}
});
// SET OR CHANGE COOKIES IN RUNTIME
cookies.set('ab-test', 42);
cookies.set('isNew', false);
cookies.set('agreed-with-gdpr', true);
// SYNC COOKIES
await cookies.sendAsync();
}
Use new Cookies()
on Client and Server separately or in the same file
import { Meteor } from 'meteor/meteor';
import { Cookies } from 'meteor/ostrio:cookies';
const cookies = new Cookies();
cookies.set('locale', 'en');
cookies.set('country', 'usa');
cookies.set('gender', 'male');
console.log(cookies.get('gender')); // "male"
console.log(cookies.has('locale')); // true
console.log(cookies.keys()); // ['locale', 'country', 'gender']
cookies.remove('locale');
console.log(cookies.get('locale')); // undefined
import { Meteor } from 'meteor/meteor';
import { Cookies } from 'meteor/ostrio:cookies';
import { WebApp } from 'meteor/webapp';
new Cookies();
WebApp.connectHandlers.use((req, res, next) => {
const cookiesInstance = req.Cookies;
cookiesInstance.set('locale', 'en');
cookiesInstance.set('country', 'usa');
cookiesInstance.set('gender', 'male');
console.log(cookiesInstance.get('gender')); // "male"
next();
});
Sometimes it is required to build temporary or separate logic based on Client's cookies. And to split logic between different modules and files
import { Meteor } from 'meteor/meteor';
import { Cookies } from 'meteor/ostrio:cookies';
// register default middleware that will handle requests and req.Cookies extension
const globalCookies = new Cookies();
// In checkout module/file
WebApp.connectHandlers.use((req, res, next) => {
if (req.Cookies.has('checkout-session')) {
const sessionId = req.Cookies.get('checkout-session');
// CHECK IF CHECKOUT SESSION IS VALID
if (isCheoutSessionValid(sessionId)) {
// FORCE-REDIRECT USER TO CHECKOUT IF SESSION IS VALID
res.statusCode = 302;
res.setHeader('Location', `https://example.com?chsessid=${sessionId}`);
res.end();
return;
}
// REMOVE CHECKOUT COOKIE IF NOT VALID OR EXPIRED
req.Cookies.remove('checkout-session');
}
next();
});
// In session module/file
const sessionCookies = new Cookies({
auto: false,
async handler(cookies) {
// FOR EXAMPLE: CHECK SESSION EXPIRATION
if (cookies.has('session-exp')) {
if (cookies.get('session-exp') < Date.now()) {
// .remove() WILL ADD `Set-Cookie` HEADER WITH expires=0 OPTION
cookies.remove('session-id');
cookies.remove('session-exp');
}
} else {
// MARK USER AS NEW
cookies.set('session-type', 'new-user');
}
}
});
// unregister handler when it isn't needed
sessionCookies.destroy();
Often cookies logic depends from URL it was called from. Access request details on handler
callback using cookies.response.req.url
{IncomingMessage} object:
import { Cookies } from 'meteor/ostrio:cookies';
new Cookies({
auto: false,
async handler(cookies) {
const url = new URL(cookies.response.req.url);
switch (url.pathname) {
case '/#/create':
// GET USER'S SELECTED PLAN ON #
const plan = url.searchParams.get('plan') || 'default-plan';
cookies.set('selected-tariff', plan);
break;
case '/shopping-cart/new':
// CREATE NEW CHECKOUT SESSION ID
cookies.set('checkout-session', Random.id());
break;
}
}
});
import { Meteor } from 'meteor/meteor';
import { Cookies } from 'meteor/ostrio:cookies';
if (Meteor.isClient) {
const cookies = new Cookies();
cookies.set('gender', 'male');
console.log(cookies.get('gender')); // "male"
console.log(cookies.keys()); // ['gender']
}
if (Meteor.isServer) {
const { WebApp } = require('meteor/webapp');
const cookiesInstance = new Cookies({
auto: false, // Disable auto-binding (optional)
handler(cookies) {
console.log(cookies.get('gender')); // "male"
}
});
WebApp.connectHandlers.use(cookiesInstance.middleware());
}
- Clone the package repository.
- Open a terminal in the cloned directory.
- Run tests using:
# Default
meteor test-packages ./
# With a custom port
meteor test-packages ./ --port 8888
- Upload and share files using βοΈ meteor-files.com β Continue interrupted file uploads without losing any progress. There is nothing that will stop Meteor from delivering your file to the desired destination
- Use β² ostr.io for Server Monitoring, Web Analytics, WebSec, Web-CRON and SEO Pre-rendering of a website
- Star on GitHub
- Star on Atmosphere
- Sponsor via GitHub
- Support via PayPal