Skip to content

Amber.js

Meta framework for building chrome extension MV3

Write the extension. Let the toolchain write the manifest.

Amber.js

What Amber replaces

Every MV3 project starts with the same wiring: a hand-maintained manifest.json whose paths must match your build output, a bundler config that emits each entrypoint in the right format, and a reload story that MV3's service worker actively fights.

Amber makes the manifest a build artifact. You declare entrypoints as objects; the toolchain resolves their real output paths and writes the JSON.

json
{
  "manifest_version": 3,
  "name": "my-extension",
  "version": "1.0.0",
  "icons": {
    "16": "assets/icon16.png",
    "32": "assets/icon32.png",
    "48": "assets/icon48.png",
    "128": "assets/icon128.png"
  },
  "action": { "default_popup": "index.html" },
  "background": {
    "service_worker": "entries/background.js",
    "type": "module"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["scripts/content-script.js"]
    }
  ]
}
ts
import {
  
defineConfig
,
BackgroundScript
,
ContentScript
,
Icons
,
Page
} from '@amber.js/bundler' export default
defineConfig
({
manifest
: {
manifest_version
: 3,
name
: 'my-extension',
version
: '1.0.0',
description
: 'Does a useful thing',
icons
: new
Icons
('public/logo.png'),
action
: {
default_popup
: new
Page
('index.html') },
background
: new
BackgroundScript
('src/background.ts'),
content_scripts
: [
new
ContentScript
('src/content-script.ts', {
matches
: ['<all_urls>']
}) ] } })

The four icon sizes on the left are files you have to produce and keep in sync. On the right they are generated from one source image at build time. The output paths on the left are strings you have to keep matching your bundler's naming config; on the right they are computed from the entrypoint you declared.

Typed messaging across contexts

Chrome gives you chrome.runtime.sendMessage and an untyped payload. Amber gives you the handler's actual signature, checked at the call site.

ts
import { 
Messaging
} from '@amber.js/core'
import type {
BackgroundChannel
} from './background'
const
background
=
Messaging
.
getBackgroundChannel
<
BackgroundChannel
>()
const
user
= await
background
.
send
('user.rename', 1, 'Ada')

Released under the MIT License.