brownies is a free, open source browsers & extensions project written in TypeScript and released under MIT. It has 2,564 GitHub stars, 61 forks and 0 open issues, and was last pushed 14 hours ago. On this registry it ranks #97 of 148 tracked projects in Browsers & Extensions, with 5 head-to-head comparisons available.

What is brownies?

Brownies is a tiny, zero-dependency TypeScript library that gives browser JavaScript developers a single object-property interface for cookies, local storage, session storage, and asynchronous database storage, plus subscribe() events for changes.

What it is

Brownies is an MIT-licensed TypeScript package distributed on npm. The repository has 2,564 stars, 61 forks, and zero open issues. It exports four storage objects: cookies, local, session, and db. It exposes them through ordinary property operations: assignment sets a value, property access reads it, and delete removes it. The db object is asynchronous, so reads use await db.token, while cookies, local, and session are read synchronously. The project is presented as a tiny package with zero dependencies, and its topics list browser, cookie, JavaScript, localStorage, sessionStorage, and storage.

The concrete problem is the gap between browser storage APIs and normal JavaScript object handling. Cookies are awkward to manipulate directly: the README warns that manually setting cookies with document.cookie or server-side code requires matching the library's encoding, which applies JSON.stringify() first and then encodeURIComponent() to remain RFC 6265 compliant. Brownies wraps that encoding so cookies retain numbers, booleans, strings, arrays, and objects instead of collapsing to strings. It also adds change notification through subscribe(), which calls a callback with the new value, including null after deletion.

Key capabilities

  • A single property interface across cookies, local, session, and db: cookies.token = 42 sets a value, cookies.token reads it, and delete cookies.token removes it.
  • Type-preserving cookie storage: numbers, booleans, strings, arrays, and objects survive a round trip, with values encoded by JSON.stringify() and encodeURIComponent() for RFC 6265 compliance.
  • Asynchronous db access: writes use db.token = 42 and deletes use delete db.token, but reads require await db.token.
  • subscribe(session, 'token', callback) change events: the callback is called with 42, 'Hello', and null as the value is set, changed, and deleted.
  • Standard iteration over storage objects with Object.keys(), Object.values(), Object.entries(), for...in, and for...of.
  • Browser delivery through npm or CDN: the package installs as brownies, and the CDN build defines a global brownies object from which cookies, local, and other parts are extracted.
  • Deleted cookie values read as null, a default the README sets to stay consistent with other local storage technologies.

Who uses it and how

  • Browser JavaScript developers who need to read and write cookies without manually composing document.cookie strings, including developers who want typed values rather than string-only cookies.
  • Front-end applications that keep persistent values in local and per-session values in session through the same assignment, read, and delete syntax.
  • Interfaces that must react to storage changes, using subscribe() on an object and a key; the README's example logs 42, 'Hello', and null as the value changes and is deleted.
  • Projects that use asynchronous db storage, where reads are awaited and writes and deletes remain synchronous-style property operations.
  • Prototyping and script-tag use: the README points to a JSFiddle playground, and browser users can load the CDN build and destructure the global brownies object.

Getting started

Install the package with npm install brownies, then import named parts such as cookies and local with ES module import or CommonJS require. Browser users can load the CDN build, which defines a global brownies object, or experiment in the linked JSFiddle playground.

How it compares

The facts provide no list of paid products and name no similar tools, so a feature-by-feature comparison would require inventing details. Within this registry, brownies stands alone as the storage wrapper described here.

When to use it โ€” and when not to

Brownies is a client-side library, so there is no database, object storage, or SMTP service to operate; the adopter provides the browser environment, and the README excerpt does not document what backs the asynchronous db storage beyond the await read pattern. Teams should not choose it if they need a fully documented database adapter, or if other code writes cookies with document.cookie and does not match the JSON.stringify() plus encodeURIComponent() encoding. The provided README excerpt also cuts off in the cookie options section, so teams needing complete operational guidance should verify the missing details before adopting it.

project readme (upstream, from github) โ€” read inline

Brownies brownies tests gzip size dependencies

Tastier cookies, local, session, and db storage in a tiny package:

import { cookies, local, db } from 'brownies';

cookies.token = 42;     // Set it
let t = cookies.token;  // Get it
delete cookies.token;   // Eat it

local.token = 42;       // Set it
let t = local.token;    // Get it
delete local.token;     // Del it

// db is ASYNC so read is different
db.token = 42;          // Set it
let t = await db.token; // Get it
delete db.token;        // Del it

Subscribe to changes in any of the objects:

import { session, subscribe } from 'brownies';

subscribe(session, 'token', value => {
  console.log(value);   // 42, 'Hello', null
});

session.token = 42;
session.token = 'Hello';
delete session.token;

You can also iterate them as expected with Object.keys(), Object.values(), etc:

cookies.token = 42;
cookies.name = 'Francisco';

console.log(Object.keys(cookies)); // token, name

for (let val of cookies) {
  console.log(val); // 42, 'Francisco'
}

Getting started

Install it with npm:

npm install brownies

Then import the different parts:

import { cookies, local, ... } from 'brownies';
const { cookies, local, ... } = require('brownies');

Or use a CDN for the browser:

<script src="https://cdn.jsdelivr.net/npm/brownies"></script>
<script>
  // Extract it since we only define `brownies` globally
  const { cookies, local, ... } = brownies;
</script>

If you just want to play, go to the JSFiddle playground.

Cookies

Manipulate cookies with the simple getter/setter interface:

import { cookies } from 'brownies';

cookies.token = 42;          // Set it
const res = cookies.token;   // Get it
delete cookies.token;        // Eat it

Cookies will retain the types that is set. This is possible thanks to the underlying library:

cookies.id = 1;
cookies.accepted = true;
cookies.name = 'Francisco';
cookies.friends = [3, 5];
cookies.user = { id: 1, accepted: true, name: 'Francisco' };
console.log(typeof cookies.id);               // 'number'
console.log(typeof cookies.accepted);         // 'boolean'
console.log(typeof cookies.name);             // 'string'
console.log(Array.isArray(cookies.friends));  // true
console.log(typeof cookies.user);             // 'object'
Warning: Manually setting cookies with document.cookie or server-side [click for details]

Values are encoded first with JSON.stringify() to allow for different types, and then with encodeURIComponent() to remain RFC 6265 compliant. See the details in the underlying library. If you are setting cookies manually, you'll have to follow the same process:

import { cookies } from 'brownies';
document.cookie = `name=${encodeURIComponent(JSON.stringify('Francisco'))}`
console.log(cookies.name);  // Francisco

To delete a item, you have to call delete on it as you would normally do with object properties:

console.log(cookies.id);  // null
cookies.id = 1;
console.log(cookies.id);  // 1
delete cookies.id;
console.log(cookies.id);  // null

Note: the default value for deleted cookies is set to null to be consistent with other local storage technologies.

You can iterate over the cookies in many different standard ways as normal:

Object.keys(cookies);
Object.values(cookies);
Object.entries(cookies);
for (let key in cookies) {}
for (let val of cookies) {}

Options

You can change the cookies options globally:

import { cookies, options } from 'brownies';

// Options with its defaults. Note that expires is set to 100 days
cookies[options] = {
  expires: 100 * 24 * 3600,     // The time to expire in seconds
  domain: false,                // The domain for the cookie
  path: '/',                    // The path for the cookie
  secure: https ? true : false  // Require the use of https
};

cookies.token = 24;  // Will be stored for ~100 days

WARNING: you should import options and then use it as a variable like cookies[options]. You CANNOT do cookies.options nor cookies['options'].

LocalStorage

For localStorage, we define local to simplify the interface:

import { local } from 'brownies';

local.token = 42;          // Set it
const res = local.token;   // Get it
delete local.token;        // Remove it

localStorage items can be set to many different standard values, and they will retain the types:

local.id = 1;
local.accepted = true;
local.name = 'Francisco';
local.friends = [3, 5];
local.user = { id: 1, accepted: true, name: 'Francisco' };
console.log(typeof local.id);               // 'number'
console.log(typeof local.accepted);         // 'boolean'
console.log(typeof local.name);             // 'string'
console.log(Array.isArray(local.friends));  // true
console.log(typeof local.user);             // 'object'

Since 2.0 we are using custom data storage to keep the types consistent, but this means that you cannot read items that were set by brownies like localStorage.getItem(KEY). Please use the local.KEY provided by brownies API instead.

To delete a item, you have to call delete on it as you would normally do with object properties:

console.log(local.id);  // null
local.id = 1;
console.log(local.id);  // 1
delete local.id;
console.log(local.id);  // null

You can iterate over the items in many different standard ways as normal:

Object.keys(local);
Object.values(local);
Object.entries(local);
for (let key in local) {}
for (let val of local) {}

So if you wanted to delete them all, you can do so by looping them easily:

for (let key in local) {
  console.log('Deleting:', key, local[key]);
  delete local[key];
}

SessionStorage

For the sessionStorage, we define session to simplify the interface:

import { session } from 'brownies';

session.token = 42;          // Set it
const res = session.token;   // Get it
delete session.token;        // Remove it

sessionStorage items can be set to many different standard values, and they will retain the types:

session.id = 1;
session.accepted = true;
session.name = 'Francisco';
session.friends = [3, 5];
session.user = { id: 1, accepted: true, name: 'Francisco' };
console.log(typeof session.id);               // 'number'
console.log(typeof session.accepted);         // 'boolean'
console.log(typeof session.name);             // 'string'
console.log(Array.isArray(session.friends));  // true
console.log(typeof session.user);             // 'object'

Since 2.0 we are using custom data storage to keep the types consistent, but this means that you cannot read items that were set by brownies like localStorage.getItem(KEY). Please use the local.KEY provided by brownies API instead.

To delete a item, you have to call delete on it as you would normally do with object properties:

console.log(session.id);  // null
session.id = 1;
console.log(session.id);  // 1
delete session.id;
console.log(session.id);  // null

You can iterate over the items in many different standard ways as normal:

Object.keys(session);
Object.values(session);
Object.entries(session);
for (let key in session) {}
for (let val of session) {}

So if you wanted to delete them all, you can do so by looping them easily:

for (let key in session) {
  console.log('Deleting:', key, session[key]);
  delete session[key];
}

Subscribe

Subscribe allows you to listen to changes to any object, including yours:

import { local, subscribe } from 'brownies';

subscribe(local, 'token', value => {
  console.log(value);   // 42, null, 'Hello'
});

local.token = 42;
delete local.token;
local.token = 'Hello';

Warning: subscribe() cannot guarantee being sync, so the above might not trigger if the end value is the same as the initial value or middle steps might not be shown.

Changes work even if you use the native API to change the values, or even if the changes happen on another tab:

import { local, subscribe } from 'brownies';

subscribe(local, 'token', value => {
  console.log(value);   // abc (string)
});

// Note that this is the native one:
localStorage.setItem('token', 'abc');

To unsubscribe, store the value returned by subscribe() and then use it with unsubscribe():

import { cookies, subscribe, unsubscribe } from 'brownies';

const id = subscribe(cookies, 'token', token => {
  console.log(token);
});

unsubscribe(id);

You can also unsubscribe by the callback, which is very useful in a React context:

import { cookies, subscribe, unsubscribe } from 'brownies';

const cb = token => console.log('NEW TOKEN:', token);
subscribe(cookies, 'token', cb);
unsubscribe(cb);

For instance, if you want to keep the user points synced across tabs with localStorage:

import { local, subscribe, unsubscribe } from 'brownies';

export default class extends React.Component {
  constructor (props) {
    super(props);
    this.state = { points: local.points };
    this.updatePoints = this.updatePoints.bind(this);
  }
  updatePoints (points) {
    this.setState({ points });
  }
  componentDidMount () {
    subscribe(local, 'points', this.updatePoints);
  }
  componentWillUnmount () {
    unsubscribe(this.updatePoints);
  }
  render () {
    return <div>Points: {this.state.points}</div>;
  }
}

Warning: try to keep the number of subscriptions low since each will incur in a performance cost.

Trivia

My former coworker made delicious brownies when leaving the company and asked me to name a library brownies. I thought it was a fantastic idea, since brownies are tastier cookies after all ๐Ÿ™‚.

This library was previously named clean-store, but I never really liked that name. The stars in this repository were transferred from the previous repository.

Frequently asked questions

Is brownies free to use?

brownies is open source under the MIT licence. There is no licence fee and no seat count โ€” you can self-host it or, where the project offers one, pay a vendor for a managed version instead.

What does brownies do?

๐Ÿซ Tastier cookies, local, session, and db storage in a tiny package. Includes subscribe() events for changes.

What is brownies written in?

brownies is primarily written in TypeScript. Its source is publicly available at https://github.com/franciscop/brownies, and it has 2,564 GitHub stars.