Skip to content

Lifecycle Scripts

Forge apps support install and uninstall lifecycle scripts that run automatically during key moments in the app's lifecycle. These scripts allow apps to prepare the system during installation and clean up when removed.


Overview

Script Trigger Purpose
Install Every time a version is deployed (dev or live) Ensure the system is prepared for the app
Uninstall When a version is revoked or the app is deleted Clean up and restore the system to its previous state

Defining Scripts

Scripts are declared in the app's manifest.json as relative file paths within the bundle:

{
  "name": "my-app",
  "version": "1.0.0",
  "scripts": {
    "install": "scripts/install.js",
    "uninstall": "scripts/uninstall.js"
  }
}

Both fields are optional — an app can define neither, one, or both.

Bundle Structure

my-app/
├── manifest.json
├── dist/
│   └── ...
└── scripts/
    ├── install.js
    └── uninstall.js

Install Scripts

Install scripts run every time a version is deployed to either the dev or live environment. This includes:

  • First-time deployment of a version
  • Redeploying after a version update
  • Explicit reinstall (via the ?reinstall= parameter)

Idempotency Requirement

Install scripts MUST be idempotent

Because install scripts run on every deployment — not just the first time — they must be safe to execute repeatedly. Always check if resources already exist before creating them, and use upsert patterns where possible.

Do:

  • Check if a component exists before creating it
  • Use upsert or "create if not exists" logic
  • Set configuration values unconditionally (overwriting is fine)

Don't:

  • Assume the script has never run before
  • Create duplicate resources on repeated runs
  • Rely on the script running only once

When Install Runs

Event Install runs? Uninstall runs?
Deploy a new version (dev or live)
Update to a newer version ✅ (new version)
Reinstall current version
Revoke a deployed version
Delete the app

Version updates never run uninstall

When deploying a new version over an existing one, only the new version's install script runs. The old version's uninstall script is not executed. This means install scripts should be written to handle upgrading from any previous state.

Error Handling

If an install script fails, the deployment is blocked — the version will not be activated. The app's status is set to failed with the error message stored for diagnostics. You can fix the script and redeploy, or trigger a reinstall.

If the install script file referenced in the manifest does not exist in the bundle, the deployment continues gracefully (treated as no script defined).


Uninstall Scripts

Uninstall scripts run when a deployed version is removed from the system. Their purpose is to clean up resources created by the install script and restore the system to its state before the app was installed.

When Uninstall Runs

  • Revoking a version — When a dev or live deployment is explicitly revoked (set to null)
  • Deleting the app — Uninstall runs for all active versions (both dev and live) before the app is removed

Error Handling

Uninstall failures are non-fatal

Unlike install scripts, uninstall script failures do not block the operation. If the uninstall script fails, the error is logged but the app is still removed. This ensures apps can always be deleted even if their cleanup logic has issues.

Best Practices

  • Remove or deactivate resources created during install (extensions, fields, etc.)
  • Deactivate rather than delete user data when possible
  • Handle the case where resources were already manually removed
  • Keep cleanup logic simple and resilient to partial states

Execution Environment

Scripts execute in a secure sandbox with a 60-second timeout and 64MB memory limit. The following libraries and services are available:

Available Libraries

Library Global Name Description
Protokol SDK $sdk Full platform SDK (authenticated)
HTTP Client http Raw HTTP client (unauthenticated)
Lodash _ Utility library
Moment moment Date/time manipulation
Handlebars Handlebars Template engine
Crypto crypto Cryptographic operations
Buffer Buffer Binary data handling
Bcrypt bcrypt Password hashing
JWT jwt JSON Web Token operations

Context Variables

Variable Type Description
$input object Input payload passed to the script
$env object App-specific environment variables
$globalEnv object Project-level global environment variables
$currentEnv string Current environment ("dev" or "live")

Error Reporting

Scripts do not have logging capabilities. The only way to surface information is by throwing errors. If an install script throws, the deployment is blocked and the error message is displayed in the UI under the app's status_message.

// Throw a descriptive error to signal failure
throw new Error("Required component 'products' not found in project");

Tip

Use descriptive error messages — they are the only diagnostic information visible when a script fails.


Using the SDK

The Protokol SDK is the primary way to interact with the platform from lifecycle scripts. Initialize it with:

const platform = new ($sdk.version("0.10").Platform)();

All SDK calls are automatically authenticated with service-level credentials scoped to the project.

Common SDK Operations

const platform = new ($sdk.version("0.10").Platform)();

// Find components
const items = await platform.component("orders").find({
  currentPage: 1,
  perPage: 10
});

// Create a record
await platform.component("settings").create({
  key: "app_config",
  value: { enabled: true }
});

// Update a record
await platform.component("settings").update(uuid, {
  value: { enabled: true, version: "2.0" }
});

// Delete a record
await platform.component("settings").delete(uuid);
const platform = new ($sdk.version("0.10").Platform)();

const users = await platform.users().find({
  currentPage: 1,
  perPage: 10
});

const user = await platform.users().get(userUuid);
const platform = new ($sdk.version("0.10").Platform)();

const result = await platform.functions().invoke("my_function", {
  param1: "value"
});
const platform = new ($sdk.version("0.10").Platform)();

// Interact with the workflow API
const workflows = await platform.workflow();
// Use http for calls to external services
const res = await http.get("https://api.example.com/data");
const res = await http.post("https://api.example.com/webhook", {
  event: "app_installed"
});

Examples

Install Script — Setting Up a Component Extension

This script adds a custom extension to an existing component during installation. It checks if the extension already exists to ensure idempotency.

const platform = new ($sdk.version("0.10").Platform)();

// Check if our extension already exists on the target component
const { settings } = await platform.component("products").settings();
const { extensions } = settings;

const extensionName = "my_app_tracking";
const existingExtension = extensions?.find(
  ext => ext.name === extensionName
);

if (!existingExtension) {
  // Add the extension with custom fields
  await platform.component("products").installExtension({
    name: extensionName,
    is_active: true,
    fields: [
      {
        key: "tracking_number",
        name: "Tracking Number",
        type: "string",
        module: "input",
        visible: true
      },
      {
        key: "carrier",
        name: "Carrier",
        type: "string",
        module: "select",
        visible: true,
        options: [
          { label: "DHL", value: "dhl" },
          { label: "FedEx", value: "fedex" },
          { label: "UPS", value: "ups" }
        ]
      }
    ]
  }, settings.version);
}

return { success: true };

Uninstall Script — Cleaning Up

The corresponding uninstall script removes the extension added during install.

const platform = new ($sdk.version("0.10").Platform)();

try {
  // Remove the extension we added
  const { settings } = await platform.component("products").settings();
  await platform.component("products").deleteExtension(
    "my_app_tracking",
    settings.version
  );
} catch (err) {
  // Extension may have been manually removed already — safe to ignore
}

return { success: true };

Install Script — Seeding Default Data

This script creates default configuration records that the app needs to function.

const platform = new ($sdk.version("0.10").Platform)();

// Upsert pattern: try to find existing, create if not found
const existing = await platform.component("app_settings").find({
  currentPage: 1,
  perPage: 1,
  filter: { app_id: "my-app" }
});

if (existing.data.length === 0) {
  await platform.component("app_settings").create({
    app_id: "my-app",
    settings: {
      notifications_enabled: true,
      sync_interval: 3600,
      retry_count: 3
    }
  });
}

return { success: true };

App Status Lifecycle

During script execution, the app transitions through the following statuses:

stateDiagram-v2
    [*] --> Ready: App created
    Ready --> Installing: Deploy triggers install script
    Installing --> Ready: Script succeeds
    Installing --> Failed: Script fails
    Failed --> Installing: Redeploy / Reinstall
Status Description
ready App is operational, no scripts running
installing Install script is currently executing
failed Install script failed — check status_message for details

Troubleshooting

Install Script Fails on Deploy

  1. Check the app's status_message for the error details
  2. Review the script for syntax errors or uncaught exceptions
  3. Ensure all API endpoints referenced in the script exist
  4. Verify the script completes within the 60-second timeout
  5. Fix the issue and redeploy, or use the reinstall option

Script Runs But Resources Aren't Created

  1. Add temporary throw statements to narrow down where execution stops
  2. Verify the SDK is initialized correctly: new ($sdk.version("0.10").Platform)()
  3. Check that component references match existing components in the project
  4. Ensure the script returns a value (even return { success: true })

Uninstall Doesn't Clean Up Everything

  1. Remember that uninstall failures are non-fatal — the app is still removed
  2. Verify the cleanup targets still exist (they may have been removed manually)
  3. Wrap cleanup operations in try/catch blocks to handle partial states