The manifest is derived
Declare entrypoints as objects in amber.config.ts. Amber resolves paths, generates icons and writes manifest.json for you.
Meta framework for building chrome extension MV3
Write the extension. Let the toolchain write the manifest.

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.
{
"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"]
}
]
}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.
Chrome gives you chrome.runtime.sendMessage and an untyped payload. Amber gives you the handler's actual signature, checked at the call site.
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')