Chrome Extension Rate Limiting — Best Practices

5 min read

Rate Limiting Patterns

Overview

Handle API rate limits and quota management in extensions. Prevents 429 errors and storage sync quota exhaustion. Applies to external APIs and Chrome API quotas.


Chrome API Quotas

API Quota Notes
storage.sync 120 writes/min Per extension
alarms Min 30 seconds Min interval
webRequest Unlimited Impacts perf

Batch writes: await chrome.storage.sync.set({a:1,b:2}) not separate calls.


Token Bucket

class Bucket {
  constructor(private cap: number, private r: number) { this.t=cap; this.l=Date.now(); }
  private t: number; private l: number;
  private rf(){const e=Date.now()-this.l;this.t=Math.min(this.cap,this.t+e*(this.r/1000));this.l=Date.now();}
  wait(n=1){this.rf();return this.t>=n?0:Math.ceil((n-this.t)/(this.r/1000));}
  take(n=1){this.rf();return this.t>=n?(this.t-=n,true):false;}
}
const lim = new Bucket(10,10);
async function fet(url:string){const w=lim.wait();if(w)await new Promise(r=>setTimeout(r,w));lim.take();return fetch(url);}

Store in chrome.storage.session for SW restart survival.


Debouncing & Throttling

// Debounced writer
class Debounce{
  private x?:number;private m=new Map<string,unknown>();
  set(k:string,v:unknown){this.m.set(k,v);if(this.x)clearTimeout(this.x);this.x=setTimeout(()=>{chrome.storage.sync.set(Object.fromEntries(this.m));this.m.clear();},500);}
}
// Alarm throttler
class Throttle{constructor(private n:string,private s=30){}
async a():Promise<boolean>{const A=await chrome.alarms.get(this.n);if(!A){await chrome.alarms.create(this.n,{delayInMinutes:this.s/60});return true;}return false;}
}

Exponential Backoff

async function retry(url:string,max=5,base=1000):Promise<Response>{
  for(let i=0;i<=max;i++){
    const r=await fetch(url);
    if(r.ok||(r.status!==429&&r.status<500))return r;
    await new Promise(x=>setTimeout(x,Math.min(base*2**i*(0.8+Math.random()*0.4),30000)));
  }throw new Error('max');
}

Request Queue

class Q{
  private q:Array<()=>Promise<void>>=[];private a=0;
  constructor(private m=3){}
  async e<T>(f:()=>Promise<T>):Promise<T>{return new Promise((rs,rj)=>{this.q.push(async()=>{try{rs(await f())}catch(e){rj(e)}});this.p();});}
  private async p(){while(this.a<this.m&&this.q.length){this.a++;const x=this.q.shift()!;await x().finally(()=>{this.a--;this.p()})}}
}

Batching Storage

class Batch{
  private m=new Map<string,unknown>();
  async set(k:string,v:unknown){this.m.set(k,v);if(!await chrome.alarms.get('f'))await chrome.alarms.create('f',{delayInMinutes:1/60});}
  async flush(){if(this.m.size){await chrome.storage.sync.set(Object.fromEntries(this.m));this.m.clear();await chrome.alarms.clear('f');}}
}

Listen to chrome.alarms.onAlarm to flush.


External API Tips


Summary

Pattern Use Benefit
Token Bucket API throttle Bursts
Debouncing Storage writes Fewer ops
Backoff 429 errors Recovery
Queue Concurrent API Control
Batching Storage Reduces writes

See Also

Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.