Skip to content

Deployment

You can deploy your Crowdin app to any hosting provider that supports Node.js. Below you can find examples of how to deploy your Crowdin app to Vercel, AWS Lambda, and Cloudflare Workers.

Vercel can deploy your Crowdin app from existing repo (GitHub, Gitlab, Bitbucket).

Go to Vercel and proceed with next steps:

  • Create a new project.
  • Under the Environment Variables section, define following variables:
    • CLIENT_ID - Crowdin App Client ID.
    • CLIENT_SECRET - Crowdin App Client Secret.
  • Add Postgres SQL Storage.
  • Storage -> Connect Store -> Create New -> Select Postgres.
  • Deploy.

Project structure:

  • Directoryroot
    • index.js
    • logo.png
    • package.json
    • package-lock.json
    • vercel.json
    • .gitignore

App configuration:

index.js
const crowdinModule = require('@crowdin/app-project-module');
crowdinModule.createApp({
baseUrl: `https://${process.env.VERCEL_URL}`,
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
name: 'Sample App',
identifier: 'sample-app',
description: 'Sample App description',
postgreConfig: {
host: process.env.POSTGRES_HOST,
user: process.env.POSTGRES_USER,
password: process.env.POSTGRES_PASSWORD,
database: process.env.POSTGRES_DATABASE,
ssl: true
},
imagePath: __dirname + '/' + 'logo.png',
customMT: {
translate: () => {
console.log('translate');
},
validate: () => {
console.log('validate');
}
}
});
vercel.json
{
"version": 2,
"builds": [
{
"src": "./index.js",
"use": "@vercel/node"
}
],
"routes": [
{
"src": "/(.*)",
"dest": "/"
}
]
}
.gitignore
/node_modules/
package.json
{
"name": "crowdin-app-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"engines": {
"node": "14.x"
},
"scripts": {
"start": "node index.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"@crowdin/app-project-module": "^1.0.0"
}
}

You can use Serverless to deploy your Crowdin app to AWS Lambda.

It uses Serverless framework so in order to deploy it to AWS you just need to install deps npm i and after run this command serverless deploy.

In this example we are using PostgreSQL as a storage (e.g. AWS RDS with PostgreSQL engine can be used). Visit the Database page to see other options.

Project structure:

  • Directoryroot
    • handler.js
    • logo.svg
    • package.json
    • env.json
    • package-lock.json
    • serverless.yml
    • .gitignore
handler.js
const serverless = require('serverless-http');
const path = require('path');
const express = require('express');
const app = express();
const crowdinApp = require('@crowdin/app-project-module');
crowdinApp.addCrowdinEndpoints(app, {
baseUrl: process.env.LAMBDA_URL,
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
name: 'Sample App',
identifier: 'sample-app',
description: 'Sample App description',
postgreConfig: {
user: process.env.PG_USER,
password: process.env.PG_PASSWORD,
database: process.env.PG_DB,
host: process.env.PG_HOST
},
imagePath: path.join(__dirname, 'logo.svg'),
projectIntegration: {
getIntegrationFiles: async () => {
return [
{
id: '12',
name: 'File from integration',
type: 'json',
parentId: '10'
},
{
id: '14',
name: 'File from integration 2',
type: 'xml',
parentId: '11'
},
{
id: '10',
name: 'Folder from integration'
},
{
id: '11',
name: 'Folder from integration 2'
}
];
},
updateCrowdin: async () => {
//TODO implement
},
updateIntegration: async () => {
//TODO implement
}
}
});
module.exports.handler = serverless(app);
serverless.yml
service: aws-node-express-api
frameworkVersion: '3'
provider:
name: aws
runtime: nodejs14.x
functions:
api:
handler: handler.handler
environment: ${file(env.json)}
url: true
env.json
{
"LAMBDA_URL": "https://<id>.execute-api.us-east-1.amazonaws.com",
"CLIENT_ID": "<client-id>",
"CLIENT_SECRET": "<client-secret>",
"PG_HOST": "<pg_host>",
"PG_USER": "<pg_user>",
"PG_PASSWORD": "<pg_pass>",
"PG_DB": "<pg_db>"
}
.gitignore
node_modules
.serverless
package.json
{
"name": "crowdin-serverless",
"version": "1.0.0",
"description": "",
"dependencies": {
"express": "^5.0.0",
"serverless-http": "^3.0.0",
"@crowdin/app-project-module": "^1.0.0"
},
"author": "",
"license": "ISC"
}

Deploy your Crowdin app to Cloudflare Workers with D1 database, KV storage for files, and static assets support.

Prerequisites: Wrangler CLI, Cloudflare account

Setup:

  1. Create D1 database: npx wrangler d1 create crowdin-app-db
  2. Create KV namespace: npx wrangler kv namespace create KVStore
  3. Update wrangler.jsonc with the generated IDs
  • Directoryroot
    • Directoryworker/
      • index.ts
      • app.ts
    • Directorypublic/
      • logo.svg
    • package.json
    • wrangler.jsonc
    • .gitignore
wrangler.jsonc
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "crowdin-app",
"main": "worker/index.ts",
"compatibility_date": "2025-01-01",
"compatibility_flags": [
"nodejs_compat",
"enable_nodejs_http_modules",
"enable_nodejs_http_server_modules"
],
"assets": {
"binding": "ASSETS",
"run_worker_first": ["/*", "!/@*", "!/src/*", "!/node_modules/*"]
},
"d1_databases": [
{
"binding": "DB",
"database_name": "crowdin-app-db",
"database_id": "your-d1-database-id"
}
],
"kv_namespaces": [
{
"binding": "KVStore",
"id": "your-kv-namespace-id"
}
],
"triggers": {
"crons": ["0 * * * *"]
},
"vars": {
"CROWDIN_CLIENT_ID": "your-client-id",
"CROWDIN_CLIENT_SECRET": "your-client-secret"
}
}
worker/index.ts
import { httpServerHandler } from 'cloudflare:node';
import { createApp } from './app';
import * as crowdinModule from '@crowdin/app-project-module';
import type { Cron } from '@crowdin/app-project-module/out/types';
import type { Server } from 'node:http';
// Close previous server on HMR reload
const previousServer = (globalThis as any).__previousServer as Server | undefined;
if (previousServer) {
previousServer.close();
}
// Module-scoped state
let handler: ExportedHandler | undefined;
let appInstance: ReturnType<typeof createApp> | undefined;
class CloudflareCron implements Cron {
private handlers: Map<string, Array<() => Promise<void>>> = new Map();
schedule(expression: string, task: () => Promise<void>): void {
if (!this.handlers.has(expression)) {
this.handlers.set(expression, []);
}
this.handlers.get(expression)?.push(task);
}
async runScheduled(expression: string): Promise<void> {
const tasks = this.handlers.get(expression) || [];
await Promise.allSettled(tasks.map(task => task()));
}
}
const cron = new CloudflareCron();
function initializeApp(env: CloudflareEnv) {
if (appInstance) {
return appInstance;
}
const app = crowdinModule.express();
const crowdinApp = createApp({
app,
clientId: env.CROWDIN_CLIENT_ID,
clientSecret: env.CROWDIN_CLIENT_SECRET,
assetsConfig: {
fetcher: env.ASSETS,
},
d1Config: {
database: env.DB,
},
fileStore: {
getFile: async (fileId: string): Promise<Buffer> => {
const data = await env.KVStore.get(fileId, 'arrayBuffer');
if (!data) {
throw new Error(`File not found: ${fileId}`);
}
return Buffer.from(data);
},
storeFile: async (content: Buffer): Promise<string> => {
const fileId = `file_${crypto.randomUUID()}`;
await env.KVStore.put(fileId, content, { expirationTtl: 86400 });
return fileId;
},
deleteFile: async (fileId: string): Promise<void> => {
await env.KVStore.delete(fileId);
}
},
cron
});
appInstance = crowdinApp;
return appInstance;
}
function initializeHandler(env: CloudflareEnv): ExportedHandler {
if (handler) {
return handler;
}
const { app } = initializeApp(env);
const port: number = 3000;
(globalThis as any).__previousServer = app.listen(port);
handler = httpServerHandler({ port });
return handler;
}
export default {
async fetch(request: Request, env: CloudflareEnv, ctx: ExecutionContext) {
return initializeHandler(env).fetch!(request, env, ctx);
},
async scheduled(controller: ScheduledController, env: CloudflareEnv, ctx: ExecutionContext) {
console.log(`Cron triggered: ${controller.cron}`);
try {
initializeApp(env);
await cron.runScheduled(controller.cron);
} catch (error) {
console.error('Error executing scheduled tasks:', error);
}
}
};
worker/app.ts
import * as crowdinModule from '@crowdin/app-project-module';
import type { AssetsConfig, FileStore, Cron, ClientConfig } from '@crowdin/app-project-module/out/types';
import type { D1StorageConfig } from '@crowdin/app-project-module/out/storage/d1';
export function createApp({
app,
clientId,
clientSecret,
assetsConfig,
d1Config,
fileStore,
cron
}: {
app: ReturnType<typeof crowdinModule.express>;
clientId: string;
clientSecret: string;
assetsConfig: AssetsConfig;
d1Config: D1StorageConfig;
fileStore: FileStore;
cron: Cron;
}) {
const configuration: ClientConfig = {
name: "My Crowdin App",
identifier: "my-crowdin-app",
description: "Crowdin app deployed on Cloudflare Workers",
clientId,
clientSecret,
disableLogsFormatter: true,
assetsConfig,
d1Config,
fileStore,
cron,
imagePath: '/logo.svg',
assetsPath: '/assets',
// Add your module configurations here
// Example: customMT, projectIntegration, etc.
};
const crowdinApp = crowdinModule.addCrowdinEndpoints(app, configuration);
return { app, crowdinApp };
}
package.json
{
"name": "crowdin-app",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "wrangler dev",
"deploy": "wrangler deploy",
"cf-typegen": "wrangler types --env-interface CloudflareEnv"
},
"dependencies": {
"@crowdin/app-project-module": "^1.0.0"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.0.0",
"wrangler": "^4.0.0"
}
}
.gitignore
node_modules
.wrangler
.dev.vars

Deploy:

  1. Install dependencies: npm install
  2. Generate types: npm run cf-typegen
  3. Deploy: npm run deploy

Cron: Configure scheduled events in wrangler.jsonc under triggers.crons using standard cron syntax.

Assets: Static files from the public/ directory are automatically served via Cloudflare’s edge network using the ASSETS binding.