This is an experimental Early Access Program (EAP) feature, offered to selected users for testing and feedback purposes. EAP features are unsupported, not usable in production environments, and subject to change without notice.
By default, Forge packages your application code before uploading by compiling TypeScript and bundling the application dependencies using Webpack.
To use a different compiler, bundling solution, or configuration, you can switch your application to manual packaging.
You can use manual packaging to let Forge CLI upload the code from the specified directory without modifications. This allows you to:
Manually packaging the application can also reduce the application size when it has many dependencies used across multiple functions.
Manual packaging is available for the Forge functions as well as UI Kit.
When using manual packaging, Forge CLI does not process the code to upload. Therefore, it does not catch errors related to wrong types, missing variables, or dependencies. These errors will instead cause the application to crash or misbehave when it runs.
To use manual packaging for the functions,
set the following app.package properties in the manifest:
1 2 3 4 5app: package: bundler: manual@2026 path: out # target directory with files to upload
Put the application backend code into the target directory (out above).
All the application's functions will be manually packaged. You cannot selectively keep the automatic packaging behavior for a subset of them.
Manual packaging for the Forge functions requires the dependencies of the
application, typically in node_modules, to be available in the directory to be
uploaded as well, unless the dependency code is bundled together with the
application code into a single file. Ensure the required dependencies are
present in the target directory so they can be imported from the application
code.
See the standard for the full requirements to package the functions.
To use manual packaging for a UI Kit resource, add the
bundler property to the individual resource and set path to a directory
containing the assets.
1 2 3 4 5resources: - key: broccoli bundler: manual@2026 path: frontend/broccoli/out
See the standard for the full requirements to package the UI Kit resources.
forge tunnel watches the target directory for changes, not the application
code. While tunneling, you need to run your own build process in watch mode
alongside Forge CLI to see changes reflected in the running application.
To use the TypeScript compiler on the backend without Webpack:
tsconfig.json to output the compiled code
to the dist directory:1 2 3 4 5 6 7 8{ // ... "compilerOptions": { // ... "outDir": "./dist" } }
manifest.yml:1 2 3 4 5 6 7# ... app: # ... package: bundler: manual@2026 path: dist
package.json:1 2 3 4 5 6 7 8 9 10 11{ // ... "scripts": { // ... // Install production dependencies into the target directory "build:dependencies": "yarn install --production --modules-folder ./dist/node_modules", // Compile the application "build": "tsc --project ./tsconfig.json" } }
yarn build:dependencies whenever dependencies change, and yarn build
before deploying the application, for example, on CI:1 2 3 4yarn build:dependencies yarn build forge deploy
1 2 3 4yarn build --watch # separately (for example, in another terminal) forge tunnel
The following example app with a custom Vite bundler has the structure:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17. ├── manifest.yml ├── package.json ├── tsconfig.json ├── src │ ├── index.ts │ ├── resolvers/ │ └── frontend/ │ ├── package.json │ ├── tsconfig.json │ ├── vite.config.ts │ ├── home.html │ ├── settings.html │ └── src │ ├── Home.tsx │ └── Settings.tsx
The frontend directory contains the frontend resource files, including the bundler config (Vite in this example), the HTML shells for each entry point, and the UI Kit files in the src directory. This resource has two entry points: home and settings.
To produce a frontend bundle with Vite:
manifest.yml:1 2 3 4 5 6 7 8 9 10resources: - key: prebuilt-multi path: prebuilt-multi/dist # output directory with compiled code bundler: manual@2026 # enable manual packaging entry: home: home.html settings: settings.html tunnel: port: 3000
home.html
1 2 3 4 5 6 7 8<!doctype html> <html> <head></head> <body> <script type="module" src="./src/Home.tsx"></script> </body> </html>
src/Home.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28import React, { useEffect, useState } from 'react'; import ForgeReconciler from '@forge/react'; import { Text } from '@forge/react'; import { invoke } from '@forge/bridge'; export const Home = ({ title }: { title: string }) => { const [data, setData] = useState<string | null>(null); useEffect(() => { invoke('getText', { example: 'my-invoke-variable' }).then((response) => { setData(response as unknown as string); }); }, []); return ( <> <Text>{title}</Text> <Text>{data ?? 'Loading...'}</Text> </> ); }; ForgeReconciler.render( <React.StrictMode> <Home title="Hello from the Home page!" /> </React.StrictMode> );
vite.config.ts. The config sets the base path to be relative to the root, and configures build settings for the output bundle that is referenced by the resource in the manifest. It also configures a local development server to be used when tunneling the app.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import { resolve } from 'path'; export default defineConfig({ root: resolve(__dirname), plugins: [react()], base: './', build: { outDir: resolve(__dirname, 'dist'), emptyOutDir: true, rollupOptions: { input: { home: resolve(__dirname, 'home.html'), settings: resolve(__dirname, 'settings.html') } } }, server: { // Port specified as the tunnel port for the deployed resource port: Number(process.env.DEV_SERVER_PORT) || 3000, strictPort: true, host: 'localhost' } });
package.json:1 2 3 4 5 6 7 8 9 10{ // ... "scripts": { // compile app into dist directory "build": "vite build --config vite.config.ts", // start dev server for tunneling "dev": "vite --config vite.config.ts" } }
yarn build before deploying the application, for example, on CI:1 2 3yarn build forge deploy
manifest.yml as the tunnel port for the resource. Then run the Forge tunnel in another terminal:1 2 3 4yarn dev # separately (for example, in another terminal) forge tunnel
Rate this page: