Creating extensions - freelensapp/freelens GitHub Wiki
This guide explains how to create a new extension for the Freelens application using the freelens-example-extension
repository as a template.
- Basic knowledge of JavaScript/TypeScript and Node.js
- Documentation for an API of the original Lens 5.5.4 from our repository or an original site.
- Git
- Node.js installed — Node.js version must match the version used by the main Freelens application
- A Node.js version manager such as NVM, asdf, or mise-en-place is highly recommended for managing Node.js versions.
- pnpm installed
- Freelens application installed (see Freelens documentation)
Create a new project based on the example extension:
git clone https://github.com/freelensapp/freelens-example-extension.git my-freelens-extension
cd my-freelens-extension
- Rename the directory (
my-freelens-extension
) as desired. - Update
package.json
:- Set
"name"
,"description"
,"author"
, etc. - Update
"repository"
and"homepage"
if public. -
Required fields:
-
"main"
: Entry point for the main process (should be"out/main/index.js"
). -
"renderer"
: Entry point for the renderer/UI (should be"out/renderer/index.js"
). -
"engines"
: Must include a"freelens"
field specifying the compatible version and"node"
matching the main app, for example:"engines": { "freelens": "^1.4.0", "node": ">= 22.15.0" }
-
- Set
-
Dependency management:
- All dependencies must be listed under
"devDependencies"
. Extensions cannot load Node.js modules at runtime. - All modules are transpiled and bundled at build time; no runtime node_modules are allowed.
-
Externalized modules:
Modules listed inexternalizeDepsPlugin
andpluginExternal
in your Vite config are provided by the Freelens main app.- These modules must be listed as
devDependencies
and their versions must match the main app. - They are not bundled with your extension.
- These modules must be listed as
- All dependencies must be listed under
-
main/
andcommon/
directories:- Files here are executed in the Node.js process (the main process).
-
Do not use JSX runtime in these files: only
*.ts
files are allowed. - Avoid importing UI libraries or anything that requires a browser environment.
-
renderer/
(andrenderer
subdirectories incommon/
):- Files here are executed in the renderer (browser) process.
- These files can use JSX and UI libraries: you can use
*.tsx
files. -
Do not require/import Node.js modules in these files (e.g.,
fs
,os
, custom node-only libraries). - Modules can be transpiled and bundled, but must be compatible with browser environments.
Extensions are technically classes loaded by the Electron main application and its renderer (Node.js and browser contexts).
All assets needed by your extension (icons, images, style files, markdown, YAML, and text files) must be bundled into the built main/index.js
and renderer/index.js
files.
-
How to bundle assets:
- Use import statements for all assets you need, e.g.:
import icon from './icon.svg'; import styles from './MyComponent.module.scss'; import helpText from './help.md?raw'; import config from './config.yaml?raw';
- The bundler (we use Vite for extensions) will include these assets directly in your JS bundles as data URIs or strings.
- Do not rely on loading external files at runtime—everything must be available inside your built JS files.
- Use import statements for all assets you need, e.g.:
-
Why?
- Extensions can be distributed as a single package and will work regardless of the host filesystem or packaging.
- This prevents missing asset errors and simplifies deployment.
To avoid CSS class name conflicts with the main application or other extensions:
-
Always use unique prefixes for CSS class names in global styles.
Example:.myext-button { ... } .myext-panel { ... }
-
Prefer CSS Modules for isolation.
- Name your style files as
*.module.scss
or*.module.css
. - Import them in your components:
import styles from './MyComponent.module.scss'; import stylesInline from './MyComponent.module.scss?inline'; // usage: <><style>{stylesInline}</style><div className={styles.button}></>
- This ensures class names are locally scoped and avoids conflicts automatically.
- Name your style files as
-
In CSS modules, you can use class names from the main application. Use
:global(ClassName)
syntax in your SCSS file. - You should not rely on or override global Freelens styles unless explicitly intended. You can use styles defined by components exposed in the Extensions API.
- You must explicitly import style files. The bundler does not automatically load any CSS or SCSS files—you must import each style file you need.
- Avoid Windtail styles. They are used by the main application and should not be added a second time by extension.
-
New APIs and Stores:
If your extension defines new API endpoints or data stores, do not explicitly register them in your extension code.- The Freelens main application automatically discovers and registers all new APIs and Stores exported by your extension.
- Simply export your API handlers or store objects as modules—the main app will handle registration.
- Do not use manual registration hooks for APIs or Stores in your extension.
Using linters and code formatters is recommended to ensure code quality and consistency.
-
Biome and Prettier can be run directly as package scripts:
- To check with Biome:
pnpm biome:check
- To check with Prettier:
pnpm prettier:check
- To check with Biome:
- You can also use Trunk Check to run multiple checks in one command:
Trunk Check checks only modified files reported by git or all files only if
pnpm trunk:check
--all
option is added to the command.
It is recommended to validate your dependencies using the Knip tool to detect unused or missing dependencies:
- After installing and building your extension, run:
pnpm knip:development pnpm build pnpm knip:production
- This helps ensure that your
devDependencies
are accurate and that your extension does not include unnecessary code.
-
During development:
The build process produces separate files for each module (preserved modules) in theout/
directory, making debugging easier. -
For distribution:
When preparing your extension for release, you should produce a single bundled file for the main process and one for the renderer.- Set
VITE_PRESERVE_MODULES="false"
before building. - Example:
VITE_PRESERVE_MODULES=false pnpm build
- The output will be a single
index.js
file for each file referenced inpackage.json
.
- Set
- Workflows for GitHub Actions (CI/CD) and Renovate Bot configuration files are used and maintained in the freelensapp organization.
- If you are hosting your extension within the
freelensapp
organization or want to reuse their automation:- You may use or copy the shared workflow and Renovate configuration files.
-
If your extension is hosted in a different repository or organization:
- These workflow and configuration files are optional.
- You may add your own CI, Renovate, or automation as appropriate for your project.
- The absence of these files does not affect extension compatibility with Freelens.
Extensions that throw errors during initialization will not work correctly.
It is crucial to verify that your extension does not emit any error messages immediately after enabling it.
-
Where to check for errors:
-
Main context (Node.js process):
- Check the terminal output where you started the Freelens application.
- Any errors from your extension’s main process code will appear here.
-
Renderer context (browser process):
- Open the Developer Tools (Console tab) in Freelens.
- Any errors from your extension’s renderer code will appear here.
-
Main context (Node.js process):
-
Best practice:
- After enabling your extension, check both the terminal and the browser’s developer console for error messages.
- Fix any errors before distributing your extension.
-
src/
– Your extension source code (withmain/
,renderer/
,common/
as subdirectories as appropriate) -
out/
– Output directory for built (bundled) code -
integration/
– Integration tests for your extension -
package.json
– Project metadata, scripts, and devDependencies
Note: There is no
extension.json
manifest. All configuration is handled viapackage.json
and/or code.
Install all dependencies using pnpm:
pnpm install
- Edit the entry file in
src/main/
for backend logic, insrc/renderer/
for UI, and share logic insrc/common/
as needed. - Follow the runtime rules:
- Only use Node.js APIs in
main/
and non-renderercommon/
modules. - Only use browser-friendly code (including JSX if needed) in
renderer/
andcommon/renderer/
. - Do not cross the boundary: never import Node.js modules into renderer code.
- Only use Node.js APIs in
- Use unique CSS class names or CSS modules for style isolation.
- Explicitly import all required style files in your components or entry files.
- Bundle all asset files (icons, text, yaml, markdown, etc.) by importing them directly so they are included in your build.
- Export APIs and Stores for automatic registration, do not register them manually.
- Build the extension:
pnpm build
- Pack to tgz file:
```bash
pnpm pack
Integration tests are located in the integration/
directory. You can copy them to Freelens main application source to run as extensions can use them directly.
- In the Freelens app, go to Extensions settings and select "Add Local Extension."
- Choose the built tgz file.
- Wait until extension is enabled.
- Restart Freelens.
- Verify your extension works as expected.
- Check both the terminal and developer console for errors immediately after enabling your extension. Resolve any errors before distribution.
- Push your extension to a public repository (like GitHub).
- Optionally, publish to npm if supported by Freelens.
- Ensure your
README.md
includes usage and installation instructions.
- Monitor changes in the Freelens API and update your extension as needed.
- Keep devDependencies up to date, especially for externalized modules and Node.js version.
- Use integration tests and code checks (linters/formatters) to ensure new changes do not break your extension.
- Regularly validate dependencies with Knip.
- Freelens Official Documentation
- freelens-example-extension
- pnpm Documentation
- Node.js Documentation
- NVM
- asdf
- mise-en-place
- Biome
- Prettier
- Trunk Check
- Knip
If you need help:
- Browse Freelens community discussions.
- Open an issue in your extension’s repository or in the main Freelens repository if it’s a general extension problem.
Happy coding!