# Overview

You can create Crowdin apps that extend the functionality of Crowdin by adding new modules to the Crowdin UI. Modules are displayed in the Crowdin UI and can be used to add new features or modify existing ones.

## Supported Modules

[Project Integration](/app-project-module/project-integration/)
  [File Processor](/app-project-module/file-processor/)
  [Context Menu](/app-project-module/tools/context-menu/)
  [Modal](/app-project-module/tools/modal/)
  [Chat](/app-project-module/tools/chat/)
  [Custom Machine Translation (MT)](/app-project-module/tools/custom-mt/)
  [Custom Spellchecker](/app-project-module/tools/custom-spellchecker/)
  [External QA Check](/app-project-module/tools/external-qa-check/)
  [AI Provider](/app-project-module/tools/ai-provider/)
  [AI Prompt Provider](/app-project-module/tools/ai-prompt-provider/)
  [API](/app-project-module/tools/api/)
  [Webhook](/app-project-module/tools/webhook/)
  [Workflow Step Type](/app-project-module/tools/workflow-step-type/)
## Other Supported Modules

The SDK supports all the other modules that are available in Crowdin:

- [profile-resources-menu](https://support.crowdin.com/developer/crowdin-apps-module-profile-resources-menu/)  - `profileResourcesMenu`
- [organization-menu](https://support.crowdin.com/developer/crowdin-apps-module-organization-menu/)  - `organizationMenu`
- [editor-right-panel](https://support.crowdin.com/developer/crowdin-apps-module-editor-right-panel/) - `editorRightPanel`
- [project-menu](https://support.crowdin.com/developer/crowdin-apps-module-project-menu/) - `projectMenu`
- [project-menu-crowdsource](https://support.crowdin.com/developer/crowdin-apps-module-project-menu-crowdsource/) - `projectMenuCrowdsource`
- [project-tools](https://support.crowdin.com/developer/crowdin-apps-module-project-tools/) - `projectTools`
- [project-reports](https://support.crowdin.com/developer/crowdin-apps-module-project-reports/) - `projectReports`

### Sample

Example of [profile-resources-menu](https://support.crowdin.com/developer/crowdin-apps-module-profile-resources-menu/) module:

```js ins="profileResourcesMenu" title="index.js"
    import crowdinModule from '@crowdin/app-project-module';

    const configuration = {
      baseUrl: 'https://123.ngrok.io',
      clientId: 'clientId',
      clientSecret: 'clientSecret',
      name: 'Sample App',
      identifier: 'sample-app',
      description: 'Sample App description',
      dbFolder: import.meta.dirname,
      imagePath: import.meta.dirname + '/logo.png',
      profileResourcesMenu: [
        {
          fileName: 'setup.html',
          uiPath: import.meta.dirname + '/public',
          environments: 'crowdin-enterprise'
        }
      ]
    };

    crowdinModule.createApp(configuration);
    ```
  ```ts ins="profileResourcesMenu" title="index.ts"
    import crowdinModule from '@crowdin/app-project-module';
    import type { ClientConfig } from '@crowdin/app-project-module';

    const configuration: ClientConfig = {
      baseUrl: 'https://123.ngrok.io',
      clientId: 'clientId',
      clientSecret: 'clientSecret',
      name: 'Sample App',
      identifier: 'sample-app',
      description: 'Sample App description',
      dbFolder: import.meta.dirname,
      imagePath: import.meta.dirname + '/logo.png',
      profileResourcesMenu: [
        {
          fileName: 'setup.html',
          uiPath: import.meta.dirname + '/public',
          environments: 'crowdin-enterprise'
        }
      ]
    };

    crowdinModule.createApp(configuration);
    ```
**Tip:** You can define UI for a module in a declarative way. See [Use React JSON Schema forms as module front-end](/app-project-module/user-interface/) for more detail.

## Configuration

| Parameter      | Description                                                           | Example                           |
|----------------|-----------------------------------------------------------------------|-----------------------------------|
| `imagePath`    | Path to the module logo (can be different image than the app logo).   | `import.meta.dirname + '/reports.png'` |
| `fileName`     | Optional, only needed if file is not `index.html`.                    | `'reports.html'`                  |
| `uiPath`       | Folder where UI of the module is located (`js`, `html`, `css` files). | `import.meta.dirname + '/public'`      |
| `environments` | Environment where the module should render.                           | `crowdin` / `enterprise`          |
| `imageUrl`     | Alternative to `imagePath` to specify if logo is hosted remotely.     | `'https://test.com/reports.png'`  |

### Combining Modules

Each module can work as an extension to other modules, being something like a configuration UI for them:

```js title="index.js"
    import crowdinModule from '@crowdin/app-project-module';

    const app = crowdinModule.express();

    const configuration = {
      baseUrl: 'https://123.ngrok.io',
      clientId: 'clientId',
      clientSecret: 'clientSecret',
      name: 'Sample App',
      identifier: 'sample-app',
      description: 'Sample App description',
      dbFolder: import.meta.dirname,
      imagePath: import.meta.dirname + '/logo.png',
      profileResourcesMenu: [
        {
          fileName: 'setup.html',
          uiPath: import.meta.dirname + '/public',
          environments: 'crowdin'
        }
      ],
      customMT: [
        {
          translate
        }
      ],
      onUninstall: cleanup
    };

    const crowdinApp = crowdinModule.addCrowdinEndpoints(app, configuration);
    const metadataStore = crowdinModule.metadataStore;

    async function cleanup({ organization, allCredentials }) {
      // Cleanup logic
      await crowdinApp.deleteMetadata(organization);
    }

    async function translate({ client, context, projectId, source, target, strings }) {
      const organization = context.jwtPayload.domain || context.jwtPayload.context.organization_id;
      const metadata = await metadataStore.getMetadata(organization);
      // do translation based on metadata
      const translations = ['hello', 'world'];
      return translations;
    }

    // extra endpoints for resources UI
    app.post('/metadata', async (req, res) => {
      // Always specify moduleKey to restrict access to authorized modules
      const { context } = await crowdinApp.establishCrowdinConnection({
        jwtToken: req.query.jwt,
        moduleKey: ['sample-app-profile-resources'] // Only this module can access
      });
      const id = `${context.jwtPayload.context.organization_id}_${context.jwtPayload.context.project_id}`;
      const organization = context.jwtPayload.domain || context.jwtPayload.context.organization_id;
      const metadata = await metadataStore.getMetadata(organization);
      await crowdinApp.saveMetadata({ id, metadata: req.body, crowdinId: organization });
      res.status(204).end();
    });

    app.get('/metadata', async (req, res) => {
      // Always specify moduleKey to restrict access to authorized modules
      const { context } = await crowdinApp.establishCrowdinConnection({
        jwtToken: req.query.jwt,
        moduleKey: ['sample-app-profile-resources'] // Only this module can access
      });
      const id = `${context.jwtPayload.context.organization_id}_${context.jwtPayload.context.project_id}`;
      const metadata = await metadataStore.getMetadata(id) || {};
      res.status(200).send(metadata);
    });

    app.listen(3000, () =>  console.log('Crowdin app started'));
    ```
  ```ts title="index.ts"
    import crowdinModule from '@crowdin/app-project-module';
    import type { ClientConfig, OnUninstallHandler } from '@crowdin/app-project-module';
    import type { CustomMTTranslateHandler } from '@crowdin/app-project-module/modules/custom-mt';

    const app = crowdinModule.express();

    const cleanup: OnUninstallHandler = async ({ organization, allCredentials }) => {
      // Cleanup logic
      await crowdinApp.deleteMetadata(organization);
    };

    const translate: CustomMTTranslateHandler = async ({ client, context, projectId, source, target, strings }) => {
      const organization = String(context.jwtPayload.domain || context.jwtPayload.context.organization_id);
      const metadata = await metadataStore.getMetadata(organization);
      // do translation based on metadata
      const translations = ['hello', 'world'];
      return translations;
    };

    const configuration: ClientConfig = {
      baseUrl: 'https://123.ngrok.io',
      clientId: 'clientId',
      clientSecret: 'clientSecret',
      name: 'Sample App',
      identifier: 'sample-app',
      description: 'Sample App description',
      dbFolder: import.meta.dirname,
      imagePath: import.meta.dirname + '/logo.png',
      profileResourcesMenu: [
        {
          fileName: 'setup.html',
          uiPath: import.meta.dirname + '/public',
          environments: 'crowdin'
        }
      ],
      customMT: [
        {
          translate
        }
      ],
      onUninstall: cleanup
    };

    const crowdinApp = crowdinModule.addCrowdinEndpoints(app, configuration);
    const metadataStore = crowdinModule.metadataStore;

    // extra endpoints for resources UI
    app.post('/metadata', async (req, res) => {
      // Always specify moduleKey to restrict access to authorized modules
      const { context } = await crowdinApp.establishCrowdinConnection({
        jwtToken: req.query.jwt as string,
        moduleKey: ['sample-app-profile-resources'] // Only this module can access
      });
      const id = `${context.jwtPayload.context.organization_id}_${context.jwtPayload.context.project_id}`;
      const organization = String(context.jwtPayload.domain || context.jwtPayload.context.organization_id);
      const metadata = await metadataStore.getMetadata(organization);
      await crowdinApp.saveMetadata({ id, metadata: req.body, crowdinId: organization });
      res.status(204).end();
    });

    app.get('/metadata', async (req, res) => {
      // Always specify moduleKey to restrict access to authorized modules
      const { context } = await crowdinApp.establishCrowdinConnection({
        jwtToken: req.query.jwt as string,
        moduleKey: ['sample-app-profile-resources'] // Only this module can access
      });
      const id = `${context.jwtPayload.context.organization_id}_${context.jwtPayload.context.project_id}`;
      const metadata = await metadataStore.getMetadata(id) || {};
      res.status(200).send(metadata);
    });

    app.listen(3000, () =>  console.log('Crowdin app started'));
    ```
  Read more about [App Metadata](/app-project-module/database/#app-metadata).