I'm a full-stack developer and security researcher who loves digging into internals, simplifying complex systems, and building tools that help developers move faster.
In this example, embeddings are generated in the Custom UI (browser). This works well and keeps the flow fully local.
However, it is also possible to run the embedding model entirely on the Forge backend (inside the resolver), while still staying aligned with Runs on Atlassian eligibility.
a separate bundle with @huggingface/transformers is built and loaded dynamically at runtime (Forge does not support dynamic imports in the default webpack build)
Small addition to this article.
In this example, embeddings are generated in the Custom UI (browser). This works well and keeps the flow fully local.
However, it is also possible to run the embedding model entirely on the Forge backend (inside the resolver), while still staying aligned with Runs on Atlassian eligibility.
This approach has a few practical differences:
You can use the same model - Xenova/all-MiniLM-L6-v2 - directly on the backend.
The setup is slightly different:
1. Add the model files into the app
The model files can be placed inside the app like this:
One important detail: model_quantized.onnx should be renamed to model.onnx.
2. Include the model files in manifest.yml
The model files need to be included in the Forge package:
Also, backend embeddings do not need the Custom UI wasm setup from the frontend example.
3. Build a separate sidecar bundle with @huggingface/transformers
In my case, I created a separate small library for this:
ai-lib/
Its package.json looks like this:
This is built separately because the default Forge webpack build does not handle the dynamic import path for the model bundle the way we need here.
4. Build for the correct architecture
For forge deploy, the ai-lib bundle must match the runtime architecture defined in manifest.yml.
For example:
If architecture is set to arm64, build the sidecar bundle for arm64:
If architecture is set to x86_64, build it for x86_64:
If architecture is not specified in manifest.yml, use x86_64.
For forge tunnel, use:
In the tunnel case, the build is intended for local development and depends on your machine architecture.
5. Backend embedding code
The vector generation code can look like this:
6. Include the built sidecar bundle in manifest.yml
After the bundle is built, include it in the app package too:
7. Load the bundle dynamically on the backend
In the backend code, the built bundle can be loaded dynamically like this:
8. Use it directly in the resolver
Then the resolver can generate embeddings on the backend:
With this approach, the frontend sends only plain text, and the vectors for semantic search are computed fully on the backend.
I created a full working example here:
forge-sql-orm-example-backend-ai