ZuploZuplo
LoginSign Up
  • Documentation
  • API Reference
Introduction
Getting Started
    Develop using the Portal
      1 - Setup Your Gateway2 - Rate Limiting3 - API Key Auth4 - Deploy5 - Dynamic Rate LimitingMCP - Quick start
    Develop Locally
      1 - Setup Your Gateway2 - Rate Limiting3 - API Key Auth
Concepts
Development
    CORSEnvironment VariablesBranch-Based DeploymentsTestingTroubleshootingGitOps vs TerraformCustom Code
    Local Development
    Guides
      Advanced Path MatchingAPI VersioningOpenAPI Server URLsConvert URLs to OpenAPIOpenAPI Extension DataPath Modification ScriptsOpenAPI OverlaysCanary Routing for EmployeesGeolocation Backend RoutingUser-Based Backend RoutingBypass a PolicyTesting GraphQL QueriesHealth ChecksPerformance TestingTroubleshooting Slow ResponsesNon-Standard PortsHandling FormDataS3 Signed URL UploadsCheck IP AddressLazy Load ConfigurationSharing Code Across ProjectsBackstage IntegrationGitHub Action Automation
Policies
Handlers
API Keys
MCP Server
MCP Gateway
AI Gateway
Developer Portal
Monetization
Deploying & Source Control
Observability
Networking & Infrastructure
Account Management
Programming API
Build with AI
Zuplo CLI
Migration Guides
Platform LimitsSecuritySupportTrust & ComplianceChangelog
powered by Zudoku
Guides

Sharing Code Across Zuplo Projects

When you have multiple Zuplo projects that share common functionality like custom policies, handlers, or utility functions, you can create a shared npm package to avoid duplicating code. This guide shows how to create a reusable TypeScript module package and automatically copy the source files into your Zuplo projects.

Overview

The approach is straightforward:

  1. Create an npm package containing your shared TypeScript code and a postinstall script that copies files to the consumer's modules folder
  2. Publish it to npm or a private registry (or reference it directly via Git)
  3. Install the package in your Zuplo projects - the postinstall script automatically copies the .ts files into ./modules

Since Zuplo compiles TypeScript at deployment time, you ship raw TypeScript source files rather than pre-compiled JavaScript. This ensures your shared code integrates seamlessly with Zuplo's build process.

Creating the Shared Package

Project Structure

Create a new npm package with the following structure:

Code
my-shared-zuplo-modules/ ├── package.json ├── scripts/ │ └── copy-to-modules.mjs ├── src/ │ ├── policies/ │ │ └── custom-auth-policy.ts │ ├── handlers/ │ │ └── custom-handler.ts │ └── utils/ │ └── helpers.ts └── README.md

Package Configuration

Configure your package.json to include the TypeScript source files and a postinstall script that copies them to the consumer's modules folder:

my-shared-zuplo-modules/package.json
{ "name": "@your-org/shared-zuplo-modules", "version": "1.0.0", "description": "Shared Zuplo modules for custom policies and handlers", "files": ["src/**/*.ts", "scripts/**/*.mjs"], "scripts": { "postinstall": "node ./scripts/copy-to-modules.mjs" }, "peerDependencies": { "@zuplo/runtime": "^1.0.0" }, "devDependencies": { "@zuplo/runtime": "^1.0.0", "typescript": "^5.0.0" } }

Key points:

  • The files array includes both the source files and the copy script
  • The postinstall script runs automatically when the package is installed
  • Use peerDependencies for @zuplo/runtime since consumers provide this
  • No build step is needed because you're shipping raw TypeScript

Copy Script

Create a script in your shared package that copies files to the consumer's modules folder. The script adds a header comment to each file indicating it was auto-generated and should not be edited directly:

my-shared-zuplo-modules/scripts/copy-to-modules.mjs
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync, } from "fs"; import { dirname, join, resolve } from "path"; import { fileURLToPath } from "url"; const __dirname = dirname(fileURLToPath(import.meta.url)); // Source: the src directory in this package const sourceDir = resolve(__dirname, "..", "src"); // Destination: the modules/shared folder in the consuming project // Navigate up from node_modules/@your-org/shared-zuplo-modules/scripts const projectRoot = resolve(__dirname, "..", "..", "..", ".."); const destDir = join(projectRoot, "modules", "shared"); // Read package.json to get package name and version const packageJson = JSON.parse( readFileSync(resolve(__dirname, "..", "package.json"), "utf-8"), ); const packageInfo = `${packageJson.name}@${packageJson.version}`; // Header comment to add to copied files const header = `/** * AUTO-GENERATED FILE - DO NOT EDIT DIRECTLY * * This file was copied from ${packageInfo} * Any changes made here will be overwritten when the package is updated. * * To modify this code, edit the source in the shared package and republish. */ `; // Recursively process and copy files function copyWithHeader(src, dest) { if (!existsSync(dest)) { mkdirSync(dest, { recursive: true }); } const entries = readdirSync(src); for (const entry of entries) { const srcPath = join(src, entry); const destPath = join(dest, entry); if (statSync(srcPath).isDirectory()) { copyWithHeader(srcPath, destPath); } else if (entry.endsWith(".ts")) { // Add header to TypeScript files const content = readFileSync(srcPath, "utf-8"); writeFileSync(destPath, header + content); } else { // Copy other files as-is cpSync(srcPath, destPath); } } } // Copy all files with headers if (existsSync(sourceDir)) { copyWithHeader(sourceDir, destDir); console.log(`✅ Copied shared modules to ${destDir}`); } else { console.warn(`⚠️ Source directory not found: ${sourceDir}`); }

This script runs automatically when someone installs your package, copying your TypeScript source files directly into their Zuplo project's modules/shared folder with a header comment indicating the source.

Example Shared Code

Create your shared modules using standard Zuplo patterns:

my-shared-zuplo-modules/src/policies/custom-auth-policy.ts
import { ZuploContext, ZuploRequest } from "@zuplo/runtime"; export interface CustomAuthOptions { headerName: string; allowedValues: string[]; } export default async function customAuthPolicy( request: ZuploRequest, context: ZuploContext, options: CustomAuthOptions, policyName: string, ): Promise<ZuploRequest | Response> { const headerValue = request.headers.get(options.headerName); if (!headerValue) { return new Response(`Missing ${options.headerName} header`, { status: 401, }); } if (!options.allowedValues.includes(headerValue)) { return new Response("Unauthorized", { status: 403 }); } return request; }
my-shared-zuplo-modules/src/utils/helpers.ts
import { ZuploContext } from "@zuplo/runtime"; export function formatRequestId(context: ZuploContext): string { return `req-${context.requestId.slice(0, 8)}`; } export function parseJsonSafely<T>(text: string): T | null { try { return JSON.parse(text) as T; } catch { return null; } }

Publishing the Package

Publish to npm or your private registry:

TerminalCode
# Public npm npm publish --access public # Private npm registry npm publish --registry https://your-registry.example.com # Or use npm link for local development npm link

Alternatively, you can reference the package directly from a Git repository without publishing:

package.json
{ "dependencies": { "@your-org/shared-zuplo-modules": "github:your-org/shared-zuplo-modules#v1.0.0" } }

Using the Shared Package in Zuplo Projects

Install the Package

In your Zuplo project, install the shared package:

TerminalCode
npm install @your-org/shared-zuplo-modules

The package's postinstall script automatically copies the TypeScript files to your modules/shared folder. After installation, your project structure looks like this:

Code
your-zuplo-project/ ├── modules/ │ ├── shared/ # Automatically copied from the shared package │ │ ├── policies/ │ │ │ └── custom-auth-policy.ts │ │ ├── handlers/ │ │ │ └── custom-handler.ts │ │ └── utils/ │ │ └── helpers.ts │ └── my-handler.ts # Your project-specific modules ├── config/ │ ├── routes.oas.json │ └── policies.json └── package.json

Import and Use the Shared Code

Import the shared modules using relative paths from your project modules:

modules/my-handler.ts
import { ZuploContext, ZuploRequest } from "@zuplo/runtime"; import { formatRequestId, parseJsonSafely } from "./shared/utils/helpers"; export default async function myHandler( request: ZuploRequest, context: ZuploContext, ): Promise<Response> { const requestId = formatRequestId(context); context.log.info(`Processing request: ${requestId}`); const body = parseJsonSafely<{ name: string }>(await request.text()); return new Response(JSON.stringify({ requestId, data: body }), { headers: { "content-type": "application/json" }, }); }

Reference shared policies in your policies.json:

config/policies.json
{ "policies": [ { "name": "custom-auth", "policyType": "custom-code-inbound", "handler": { "export": "default", "module": "$import(./modules/shared/policies/custom-auth-policy)", "options": { "headerName": "x-api-key", "allowedValues": ["key1", "key2"] } } } ] }

Version Management

To ensure consistency, pin your shared package versions:

package.json
{ "dependencies": { "@your-org/shared-zuplo-modules": "1.2.3" } }

Use a lockfile (package-lock.json or pnpm-lock.yaml) and commit it to ensure all team members and CI/CD pipelines use the same version.

Source Control

Commit the Copied Files

The copied shared modules must be committed to your Git repository. Zuplo deployments require all source files to be present in the repository - they are not generated during the build process.

After installing or updating the shared package, commit the changes:

TerminalCode
npm install @your-org/shared-zuplo-modules git add modules/shared/ git commit -m "Update shared modules to v1.2.3"

The header comments added by the copy script help identify these files as generated code that should not be edited directly. If you need to make changes, update the source in the shared package and republish.

Updating Shared Modules

When you update the shared package version, the postinstall script overwrites the existing files with the new version. Review the changes before committing:

TerminalCode
npm update @your-org/shared-zuplo-modules git diff modules/shared/ git add modules/shared/ git commit -m "Update shared modules to v1.3.0"

Troubleshooting

Files Not Copying

If files aren't being copied after installing the shared package:

  1. Verify the shared package is installed: ls node_modules/@your-org/shared-zuplo-modules
  2. Check the package's postinstall script ran by looking for the success message in the install output
  3. Verify the scripts/copy-to-modules.mjs file is included in the package's files array
  4. Check the path calculations in the copy script are correct for your package structure

TypeScript Errors

If you see TypeScript errors after copying:

  1. Ensure @zuplo/runtime versions match between the shared package and your project
  2. Check that all required dependencies are available
  3. Run npm run typecheck to identify specific issues

Import Path Issues

Use relative imports from your modules:

Code
// ✅ Correct - relative path from your module import { helper } from "./shared/utils/helpers"; // ❌ Incorrect - absolute or package-style import import { helper } from "@your-org/shared-zuplo-modules/utils/helpers";

Related Resources

  • Share code across request handlers and policies
  • Node Modules
  • Custom Code Inbound Policy
  • Custom Handler
Edit this page
Last modified on December 10, 2025
Lazy Load ConfigurationBackstage Integration
On this page
  • Overview
  • Creating the Shared Package
    • Project Structure
    • Package Configuration
    • Copy Script
    • Example Shared Code
    • Publishing the Package
  • Using the Shared Package in Zuplo Projects
    • Install the Package
    • Import and Use the Shared Code
  • Version Management
  • Source Control
    • Commit the Copied Files
    • Updating Shared Modules
  • Troubleshooting
    • Files Not Copying
    • TypeScript Errors
    • Import Path Issues
  • Related Resources
JSON
Javascript
TypeScript
TypeScript
JSON
TypeScript
JSON
JSON
TypeScript