This repository is a reusable starter architecture for TypeScript browser-extension products with AI analysis and a default server-side execution boundary. It is a template for creating independent product repositories, not a multi-product platform or a shared application monorepo.
Each product created from the template gets its own repository, implementation, release lifecycle, and product decisions. The three packages are the internal structure of one generated product:
Originally created by Mikhail Perepletchikov.
packages/core Browser/server-safe contracts, schemas, pure processing, and analysis types
packages/extension Browser extension runtime and React side panel
packages/server Default server-side API, analysis application, and provider concerns
The repository contains project scaffolding only. The package boundaries, strict TypeScript setup, multi-browser Manifest V3 builds, typed runtime/build-time configuration, security defaults, CI workflow, and architecture documentation are ready. The server includes a deployment-ready health endpoint, a generic analysis boundary, and a product-neutral registry with OpenAI and Anthropic adapters. Page capture, extraction, concrete analysis schemas/prompts, product API routes, and UI behavior must be implemented by the product repository created from this template.
- Node.js 22.12.0 or newer;
- pnpm 10.18.3;
- Chrome or Edge for the Chromium build, or Firefox for the Firefox build.
The package manager and Node version are declared in package.json and .nvmrc.
pnpm install
pnpm checkpnpm check runs formatting, linting, strict typechecking, tests, and package builds. The scaffold
includes focused configuration, analysis boundary, provider, and health endpoint tests.
Useful individual commands:
pnpm format # Format files
pnpm format:check # Check formatting
pnpm lint # Run ESLint
pnpm typecheck # Typecheck workspace packages
pnpm test # Run Vitest
pnpm build # Build workspace packagesUse pnpm for this workspace. The configured test script is pnpm test; there is no test:run
script.
- Enable Template repository in the GitHub repository's Settings page.
- Choose Use this template and create a new product repository.
- Clone the new repository and install dependencies with
pnpm install. - Complete the bootstrap checklist below.
- Rename the root package, extension manifest name/description, HTML title, and README.
- Update the Firefox manifest's
browser_specific_settings.gecko.idand data collection declaration for the product before signing or publishing it. - Fill in docs/product.md with the product's problem, first workflow, data handling, and runtime needs.
- Copy .env.example to
.envand set only the local values the product needs. - Keep credentials in the ignored
.envfile or the hosting provider's secret store; never put them inVITE_variables or extension code. - Keep the default server runtime, or document an alternative analysis runtime before removing the server package and deployment adapter.
- Add product-owned analysis input/output schemas, prompt, model configuration, and API route; select a built-in provider or add a tested adapter to the server registry.
- Configure a public Flagsmith environment key only if the product uses remote feature flags.
- Implement the smallest product workflow and add product-specific contracts, tests, and ADRs.
- Run
pnpm checkbefore the first release.
The package names core, extension, and server are private workspace names and can remain
generic. Rename them only if the product needs a different internal convention; they are not
published libraries.
The extension includes a neutral Flagsmith/OpenFeature provider boundary because generated products may need this capability. Configure the public Flagsmith environment key in the local environment when a product wires the provider into its entry point:
VITE_FLAGSMITH_ENV_KEY=your_public_environment_keyThe template does not define feature names, payload schemas, hooks, or product defaults. Add those
in the generated product and validate remote flag payloads with Zod. Without an environment key, the
provider uses an empty in-memory implementation for local development and tests. Initialize it from
the product's extension entry point with configureFeatureFlags(). Never bundle a Flagsmith admin or
server key.
Build both unpacked extension targets with:
pnpm buildThe build writes packages/extension/dist/chrome for Chrome and Edge and
packages/extension/dist/firefox for Firefox. To build one target, use:
pnpm --filter extension build:chrome
pnpm --filter extension build:firefoxFor Chrome or Edge, open the extensions page, enable Developer mode, choose Load unpacked, and
select packages/extension/dist/chrome. For Firefox, open about:debugging, choose This
Firefox, select Load Temporary Add-on, and open packages/extension/dist/firefox/manifest.json.
The Firefox manifest contains a template extension ID and a none data collection declaration.
Replace both with the product's stable Firefox ID and actual data flows before publishing to
addons.mozilla.org. Keep browser API calls behind packages/extension/src/platform; product entry
points can use the exported extensionPlatform contract without importing chrome.* or
browser.* directly.
The root railway.json provides a minimal single-service deployment path for the
default server package. Railway's Railpack builds the server workspace and starts it with the
package's start script. The configuration does not assume a database, Redis, persistent volume,
custom domain, or product-specific service name.
Create a Railway service from this repository and deploy it. Railway supplies the PORT environment
variable; the server listens on that port on 0.0.0.0. The /health endpoint returns HTTP 200 with
{"status":"ok"} and is used by Railway's health check. Add product-specific environment variables
in Railway after the product adds the corresponding server behavior. The committed .env.example
is for local development and documents the baseline variable expectations.
- Keep domain contracts and deterministic processing independent of Chrome APIs, React, Express, Node-only modules, and AI SDKs.
- Keep browser API calls behind the extension platform boundary and emit separate manifests for browser families whose background or panel APIs differ.
- Capture and clean page content locally where the browser has access to the rendered page.
- Never send raw DOM objects or unbounded raw HTML through extension messages or to a server.
- Perform deterministic cleanup, normalization, budgeting, and fingerprinting before semantic AI analysis.
- Keep provider credentials, prompt construction, and provider HTTP/SDK details behind the default server-side application boundary. A different analysis runtime requires an explicit product decision.
- Bound and strictly parse server JSON request bodies before product route validation; use the application service's caller-owned cancellation signal for stale or abandoned extension work.
- Validate untrusted JSON, environment values, messages, persisted state, and AI output at runtime with Zod.
- Keep the service worker responsible for extension job lifecycle and the side panel responsible for presentation.
- Prefer session-only page state and avoid durable history until a product requirement justifies it.
- Add an abstraction only when a second implementation or a clear test boundary makes it useful.
The committed .env.example is a neutral template for server/API and
extension build-time settings. The server configuration parses NODE_ENV, PORT, provider
selection, model, credential, timeout, and output-token values; the extension configuration parses
VITE_API_BASE_URL and the public Flagsmith key. To create a local file:
cp .env.example .env.env is ignored by Git and must never be committed. Provider credentials belong only in the
ignored .env file or the hosting provider's secret store; they must never enter extension code.
AI_MODEL and AI_API_KEY are fallback values for the selected provider, while OPENAI_* and
ANTHROPIC_* values are provider-specific overrides.
The server's provider contract,
registry, and built-in
provider adapters keep vendor request formats out of
product application services. Products provide their own input/output schemas and prompt through
AiProviderAdapterOptions, then resolve the configured provider in their server composition root:
const provider = createConfiguredAiProvider(
createDefaultAiProviderRegistry<AnalysisInput>({
openai: { systemInstruction: productPrompt },
anthropic: { systemInstruction: productPrompt },
}),
config.ai,
);Keep the resulting provider on the server. The application service still validates model output against the product-owned Zod schema before returning it to the extension. Products may also attach an optional content-free usage diagnostics sink to record provider, model, request-size, latency, and available token metadata without logging prompts, responses, or page content. The template does not choose prompts, schemas, failover, routing, or diagnostics retention policy for a generated product.
- Contribution guidelines explain the DCO sign-off and pull request expectations.
- Architecture notes explain the package boundaries and first-slice rules.
- Security defaults record extension, server, and repository safeguards.
- Bootstrap ADR records the initial template decisions.
- Feature flag ADR records the shared Flagsmith/OpenFeature boundary.
- Multi-browser packaging ADR records the supported Chrome/Edge and Firefox targets.
- Multi-provider AI ADR records the server-side provider registry and adapter boundary.
- Bounded analysis lifecycle ADR records HTTP request limits and caller-controlled cancellation.
- AI usage diagnostics ADR records the optional, content-free provider usage telemetry boundary.
- ADR template provides the format for product decisions.
- Product definition provides the product-specific planning starting point.
- Development instruction set defines engineering standards.
- CI workflow runs checks and builds on pushes and pull requests.
The intended GitHub repository name is column-extension-template. Renaming the GitHub repository
does not require renaming the local folder, but a matching local folder can make the workspace less
confusing. After the GitHub rename, update the local remote if GitHub has not already redirected it:
git remote set-url origin git@github.com:persys-au/column-extension-template.gitThe root package name matches the intended repository name. The internal package names remain
generic and private. The lockfile changes only when package names or dependency resolution changes;
build output should be regenerated with pnpm build and should not be committed.