https://github.com/addon-stack/inject-script
A lightweight, TypeScript-ready library for injecting JavaScript functions or external scripts into Chrome extension tabs and frames (Manifest V2 & V3).
https://github.com/addon-stack/inject-script
chrome chrome-extension edge firefox opera safari script
Last synced: 13 days ago
JSON representation
A lightweight, TypeScript-ready library for injecting JavaScript functions or external scripts into Chrome extension tabs and frames (Manifest V2 & V3).
- Host: GitHub
- URL: https://github.com/addon-stack/inject-script
- Owner: addon-stack
- License: mit
- Created: 2025-06-20T11:45:43.000Z (8 months ago)
- Default Branch: main
- Last Pushed: 2026-01-17T21:04:19.000Z (about 1 month ago)
- Last Synced: 2026-01-18T07:51:00.824Z (about 1 month ago)
- Topics: chrome, chrome-extension, edge, firefox, opera, safari, script
- Language: TypeScript
- Homepage:
- Size: 143 KB
- Stars: 2
- Watchers: 0
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE.md
- Code of conduct: CODE_OF_CONDUCT.md
- Security: SECURITY.md
Awesome Lists containing this project
README
# @addon-core/inject-script
[](https://www.npmjs.com/package/@addon-core/inject-script)
[](https://www.npmjs.com/package/@addon-core/inject-script)
[](LICENSE.md)
[](https://github.com/addon-stack/inject-script/actions/workflows/ci.yml)
A lightweight, TypeScript-ready library for injecting JavaScript functions and external script files into browser extension pages. It automatically detects Manifest V2/V3 and uses the appropriate API implementation.
## Installation
### npm:
```bash
npm install @addon-core/inject-script
```
### pnpm:
```bash
pnpm add @addon-core/inject-script
```
### yarn:
```bash
yarn add @addon-core/inject-script
```
## Quick Start
```ts
import injectScript, { type InjectScriptOptions } from "@addon-core/inject-script";
// Initialize an injector for a specific tab
const injector = injectScript({
tabId: 123,
frameId: false, // top frame only
matchAboutBlank: true, // include about:blank and similar pages
runAt: "document_idle", // injection timing (MV2)
// timeFallback: 5000, // (MV2) default timeout is 4000 ms
// world: 'ISOLATED', // (MV3) execution world
// documentId: 'abc123', // (MV3) target by documentId
} satisfies InjectScriptOptions);
// Execute a function in the page context (for all target frames)
const results = await injector.run(
(msg: string) => {
console.log(msg);
return `Echo: ${msg}`;
},
["Hello from the extension!"]
);
// Inject one or more external files
await injector.file("scripts/content.js");
await injector.file(["scripts/lib.js", "scripts/util.js"]);
```
## Features
- Unified API for Manifest V2 and V3 (version detection via `@addon-core/browser`).
- Inject functions (`run`) and files (`file`).
- Precise targeting: top frame, specific `frameId[]`, all frames, or (MV3) `documentId[]`.
- `world` support (MV3): `MAIN`/`ISOLATED`; instant injection when `runAt: 'document_start'`.
- Strongly-typed results: returns an array of `InjectionResult>` (one per frame).
- Update options on the fly with `options()`.
## API
### `injectScript(options: InjectScriptOptions): InjectScriptContract`
Creates and returns a new script injector. The implementation is chosen internally based on your manifest (MV2/MV3).
#### Contract
```ts
interface InjectScriptContract {
run(
func: (...args: A) => R,
args?: A
): Promise>[]>;
file(files: string | string[]): Promise;
options(options: Partial): this;
}
```
#### Options
```ts
interface InjectScriptOptions {
tabId: number;
frameId?: boolean | number | number[];
matchAboutBlank?: boolean; // defaults to true (MV2/MV3)
// MV2
runAt?: chrome.extensionTypes.RunAt; // 'document_start' | 'document_end' | 'document_idle'
timeFallback?: number; // timeout in ms, default 4000
// MV3
world?: chrome.scripting.ExecutionWorld | `${chrome.scripting.ExecutionWorld}`; // 'MAIN' | 'ISOLATED'
documentId?: string | string[]; // Firefox does not support documentIds in target
}
```
- `frameId`: `true` — all frames; a number or array — specific `frameId`s; `false` or undefined — top frame only.
- `matchAboutBlank`: if omitted, the library enables it by default (`true`).
- `runAt`: Chrome default is `document_idle` (when not specified).
- `timeFallback` (MV2): if results do not arrive in time, the promise will be rejected with an error.
- `world` (MV3): sets the execution world. When `runAt: 'document_start'`, `injectImmediately` is enabled.
- `documentId` (MV3): target specific documents. Firefox does not support `documentIds`; the library gracefully avoids using them there.
## Examples
Inject into specific frames:
```ts
const injector = injectScript({ tabId: 123, frameId: [0, 2] });
const results = await injector.run(() => window.location.href);
console.log(results.map(r => ({ frameId: r.frameId, url: r.result })));
```
All frames:
```ts
await injectScript({ tabId: 123, frameId: true }).file(["a.js", "b.js"]);
```
MV3: target by documentId and choose execution world:
```ts
await injectScript({
tabId: 123,
documentId: ["doc-1", "doc-2"],
world: "MAIN",
}).run(() => ({ ready: document.readyState }));
```
Update options on the fly:
```ts
const inj = injectScript({ tabId: 123, frameId: false });
await inj.options({ frameId: true }).file("content.js");
```
## MV2/MV3 Compatibility
- MV2: the library serializes your function and arguments, executes the code in target frames, and returns results via `chrome.runtime.sendMessage`.
- If your function throws, the result for that frame will be `undefined`, and the error will be logged in the page DevTools console.
- Timeout is controlled by the `timeFallback` option (default 4000 ms).
- Result order: the top frame (`frameId = 0`) is the first element in the array.
- MV3: uses `chrome.scripting.executeScript` with a properly constructed `target` (`tabId`, `frameIds`/`allFrames`, or `documentIds` when available) and `world`/`injectImmediately` options.
- Firefox does not support `documentIds` — the library will automatically avoid using them.
## Recipes
- Inject as early as possible:
- Set `runAt: 'document_start'` (MV2); in MV3 this enables `injectImmediately: true`.
- Inject into an isolated world (MV3):
- `world: 'ISOLATED'` — your code won’t conflict with the page script.
- Performance:
- Group files in a single `file([..])` call when possible to reduce overhead.
- Safety:
- Functions passed to `run` should be self-contained: rely only on what’s available in the page context. In MV2 they are string-serialized.
## Troubleshooting
- Timeout error (MV2): increase `timeFallback`.
- `undefined` result (MV2): check the page console — the function may have thrown.
- Nothing happens:
- Ensure your extension has permissions for the target tab/frame(s).
- Verify `tabId`, `frameId`/`documentId`, and the `runAt` timing.
- Conflicts with page code (MV3): use `world: 'ISOLATED'`.