localStorage: salvar dados no navegador com JS
localStorage guarda só string, é síncrono e devolve null em chave inexistente — com JSON.stringify, o limite de 5MB e o erro de salvar objeto sem converter.
localStorage guarda pares chave-valor no navegador do usuário, presos ao
domínio do site, e eles sobrevivem a fechar a aba e desligar o computador. A API
inteira são cinco métodos:
localStorage.setItem('cliente', 'Ana Souza');
console.log(localStorage.getItem('cliente'));Duas características decidem como você vai usar isso na prática: só existe
string lá dentro — objeto precisa de JSON.stringify na ida e JSON.parse na
volta — e tudo é síncrono, o que significa que uma leitura grande trava a
tela enquanto acontece. O resto desta lição é o que essas duas coisas provocam
num carrinho de compras.
O localStorage é um armazenamento síncrono de strings por origem. Em
palavras simples, cada domínio ganha uma gaveta de texto, e toda leitura ou
escrita ocupa a thread da página até terminar.
A gaveta do domínio só aceita etiquetas de texto
Imagine um arquivo de fichas em que cada gaveta pertence a uma loja e cada
registro aceita apenas uma etiqueta escrita. Para guardar uma ficha complexa,
você precisa transformá-la em texto; para usá-la de novo, precisa reconstruir os
campos. JSON.stringify e JSON.parse fazem essa passagem, mas não mudam a API
do localStorage: ali dentro continua existindo string.
Abra a área Application do DevTools antes do primeiro exemplo. Para cada
setItem, preveja chave, valor visível e tipo devolvido por getItem; depois
confira os três. Recarregue a página e repita a leitura para verificar, em vez de
apenas assumir, a persistência.
Você pode passar qualquer valor para setItem. Ele converte para string antes de
guardar, sem avisar:
localStorage.setItem('total', 199.9);
localStorage.setItem('freteGratis', true);
localStorage.setItem('cupom', null);
const total = localStorage.getItem('total');
const frete = localStorage.getItem('freteGratis');
console.log(total, typeof total);
console.log(frete, typeof frete);
console.log(localStorage.getItem('cupom'), typeof localStorage.getItem('cupom'));O terceiro caso é o mais cruel: null virou a string "null", com quatro
caracteres, que é um valor perfeitamente verdadeiro num if. E as consequências
aparecem na primeira conta e na primeira comparação:
const total = localStorage.getItem('total');
const frete = localStorage.getItem('freteGratis');
console.log('soma:', total + 10);
console.log('condicao:', frete === true, '|', frete === 'true');199.910 é "199.9" concatenado com 10. E frete === true é false, porque
está comparando string com booleano. Converter na leitura resolve os dois:
Number(localStorage.getItem('total')) e localStorage.getItem('freteGratis') === 'true'.
Objeto: JSON.stringify na ida, JSON.parse na volta
const carrinho = {
cliente: 'Ana Souza',
itens: [
{ id: 7712, nome: 'Teclado mecânico', preco: 289.9, qtd: 1 },
{ id: 8830, nome: 'Mouse sem fio', preco: 149.9, qtd: 2 },
],
};
localStorage.setItem('carrinho', JSON.stringify(carrinho));
console.log(localStorage.getItem('carrinho'));
const salvo = JSON.parse(localStorage.getItem('carrinho'));
console.log(salvo.itens.length, 'itens |', salvo.itens[0].nome);
const total = salvo.itens.reduce((soma, i) => soma + i.preco * i.qtd, 0);
console.log('total: R$', total.toFixed(2));Esse é o par que resolve o rascunho de formulário: no submit que você viu em
validar formulário com JavaScript,
grave o objeto; no carregamento da página, leia e devolva os valores aos campos.
O que voltou do JSON.parse é um objeto novo, não o mesmo de antes. Isso
importa: Date volta como string, undefined some, Map, Set e função não
sobrevivem à viagem. Se o seu objeto tem uma data, guarde em ISO
(new Date().toISOString()) e reconstrua na leitura.
getItem devolve null, nunca undefined
console.log('chave que nunca existiu:', localStorage.getItem('endereco'));
console.log('typeof:', typeof localStorage.getItem('endereco'));
console.log('length:', localStorage.length, '| key(0):', localStorage.key(0));
localStorage.removeItem('cupom');
console.log('depois do removeItem:', localStorage.length);
localStorage.clear();
console.log('depois do clear:', localStorage.length);A diferença entre null e undefined parece detalhe até você escrever
if (valor === undefined) e o if nunca entrar. Use === null, ou o
optional chaining e o ??, que tratam os
dois de uma vez.
Há uma coincidência feliz aqui: JSON.parse(null) devolve null em vez de
estourar, porque null vira a string "null", que é JSON válido.
console.log('JSON.parse(null) =>', JSON.parse(null));
console.log('JSON.parse(localStorage.getItem("inexistente")) =>', JSON.parse(localStorage.getItem('inexistente')));Ou seja: ler uma chave que não existe não quebra. Ler uma chave com conteúdo corrompido, sim — e é o próximo assunto.
É síncrono, e isso é um problema de performance
Não há await, não há callback: a linha seguinte já enxerga o dado.
localStorage.setItem('carrinho', JSON.stringify(carrinho));
console.log('disponível na linha seguinte?', localStorage.getItem('carrinho') !== null);A simplicidade tem preço: enquanto o setItem acontece, a thread principal está
parada — nada de renderizar, nada de responder a clique. Com 200 bytes ninguém
percebe. Com um histórico de pedidos inteiro, percebe.
const historico = Array.from({ length: 5000 }, (_, i) => ({
id: 7000 + i, nome: 'Teclado mecânico RGB', preco: 289.9, comprado: '2026-07-14',
}));
const json = JSON.stringify(historico);
console.log('pedidos :', historico.length);
console.log('caracteres :', json.length);
console.log('MB aprox (UTF-16):', (json.length * 2 / 1024 / 1024).toFixed(2));Cinco mil pedidos já ocupam 0,77 MB de um orçamento que gira em torno de 5 MB
por origem na maioria dos navegadores — número que não está na especificação e
varia entre eles. Passou do limite, o setItem lança QuotaExceededError.
| guardar em | tamanho típico | vai no request | some quando |
|---|---|---|---|
localStorage |
~5 MB por origem | não | o usuário limpa os dados do site |
sessionStorage |
~5 MB por aba | não | a aba fecha |
| cookie | ~4 KB por cookie | sim, em toda requisição | expira ou é apagado |
| IndexedDB | centenas de MB | não | o usuário limpa os dados do site |
Se o dado é grande ou binário, o destino é IndexedDB — que é assíncrono justamente por isso.
Erros comuns
"[object Object]" is not valid JSON
O erro mais frequente da API inteira: guardar objeto sem JSON.stringify.
const carrinho = { cliente: 'Ana Souza', itens: [{ nome: 'Teclado mecânico', preco: 289.9 }] };
localStorage.setItem('carrinho', carrinho);
console.log('guardado:', localStorage.getItem('carrinho'));
const salvo = JSON.parse(localStorage.getItem('carrinho'));
console.log(salvo.itens.length);SyntaxError: “[object Object]” is not valid JSON at JSON.parse (<anonymous>) at file:///private/tmp/loja/carrinho-storage.mjs:8:20 at ModuleJob.run (node:internal/modules/esm/module_job:439:25)
Node.js v24.16.0
Repare que o setItem não reclamou. Ele converteu o objeto com o
toString() padrão, que produz a string literal [object Object], e guardou
isso com toda a tranquilidade. O erro só aparece no JSON.parse, que pode ser em
outro arquivo, em outra sessão, uma semana depois. Se você viu [object Object]
em qualquer lugar, procure por um stringify faltando.
Confiar que o conteúdo salvo continua válido
O usuário pode editar o localStorage pelo DevTools, uma versão antiga do seu
código pode ter salvado outro formato, um setItem pode ter sido interrompido.
JSON.parse num conteúdo quebrado derruba a página inteira — então a leitura
sempre vai dentro de um try:
function lerJson(chave, padrao) {
const bruto = localStorage.getItem(chave);
if (bruto === null) return padrao;
try {
return JSON.parse(bruto);
} catch {
localStorage.removeItem(chave);
return padrao;
}
}
localStorage.setItem('carrinho', JSON.stringify({ itens: [{ nome: 'Teclado mecânico' }] }));
localStorage.setItem('cupom', '{quebrado');
console.log(lerJson('carrinho', { itens: [] }).itens.length);
console.log(lerJson('cupom', null));
console.log(lerJson('endereco', { uf: 'SP' }));
console.log('cupom quebrado foi apagado?', localStorage.getItem('cupom') === null);Três caminhos cobertos numa função de dez linhas: chave válida, chave corrompida (que é apagada em vez de ficar quebrando para sempre) e chave ausente.
Assumir que localStorage existe
Em aba anônima de alguns navegadores, com cookies de terceiros bloqueados ou
dentro de um <iframe> de outra origem, o simples acesso a localStorage lança
exceção. Um teste de escrita resolve:
function storageDisponivel() {
try {
localStorage.setItem('__teste__', '1');
localStorage.removeItem('__teste__');
return true;
} catch {
return false;
}
}
console.log('localStorage utilizável?', storageDisponivel());Se der false, o site precisa continuar funcionando — sem rascunho salvo, mas
funcionando.
Como usar sem se arrepender
- Uma chave com versão:
carrinho:v1. Quando o formato mudar, você lê a antiga, converte e escreve a nova, sem quebrar quem já tinha dados. - Prefixe por domínio do problema:
loja:carrinho,loja:endereco. OlocalStorageé compartilhado por todo o site, incluindo scripts de terceiros. - Uma função para ler, uma para gravar, ambas com
try. NuncaJSON.parseespalhado pelo código. - Nada sensível. Token, CPF, endereço completo — qualquer script da página lê tudo.
- Não é banco de dados. É cache e conveniência: rascunho de formulário, tema escolhido, carrinho antes do login. A fonte da verdade fica no servidor.
A próxima lição sai da memória e vai para a barra de endereço: window.location e redirecionamento, onde a query string vira dado. O guia completo de JavaScript mostra a trilha inteira em ordem.
Perguntas frequentes
Quanto tempo os dados ficam no localStorage?
Posso guardar o token de login no localStorage?
Qual a diferença para o sessionStorage?
Dá para saber quando outra aba mudou o localStorage?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
Todo o código deste artigo foi executado em Node 24.16.0, e as saídas exibidas são as reais — como produzimos este conteúdo.
Fontes consultadas
- MDN — Window: propriedade localStorage — developer.mozilla.org
- MDN — Usando a API Web Storage — developer.mozilla.org
- HTML Standard — Web storage — html.spec.whatwg.org


