Is there a built-in way in n8n to have a primary node with several action nodes nested inside?

I’m currently developing a custom node in n8n, which I’m referring to as Company. When I look for it in the node panel, I’m seeing the two action nodes I’ve created (authenticate and upload file) displayed separately. The issue arises when I select one of those actions, as a dropdown appears at the top, allowing me to pick from the action nodes. I want a setup similar to what S3 offers, where all actions coexist within a single main node.

Here’s what my setup looks like:

package.json configuration:

"nodes": {
  "main": [
    "dist/nodes/main/authenticate.node.js",
    "dist/nodes/main/uploadFile.node.js"
  ]
}

Main node structure:

export class Company implements INodeType {
  description: INodeTypeDescription = {
    displayName: 'Company',
    name: 'Company',
    icon: 'file:Company.png',
    group: ['transform'],
    version: 1,
    description: 'Create a project and translate a file using this node.',
    defaults: {
      name: 'Company',
      color: '#4B8BBE',
    },
    inputs: ['main'],
    outputs: ['main'],
    properties: [
      {
        displayName: 'Operation',
        name: 'operation',
        type: 'options',
        options: [
          {
            displayName: 'Authenticate',
            name: 'Authenticate',
            value: 'Authenticate',
            description: 'Log into your account',
          },
          {
            displayName: 'Upload File',
            name: 'uploadFile',
            value: 'uploadFile',
            description: 'Upload a file to Company',
          },
        ],
        default: 'Authenticate',
        description: 'Select the operation to perform',
      },
      {
        displayName: 'Username',
        name: 'username',
        type: 'string',
        default: '',
        displayOptions: {
          show: {
            operation: ['Authenticate'],
          },
        },
        description: 'The username for logging in',
        required: true,
      },
      {
        displayName: 'Password',
        name: 'password',
        type: 'string',
        typeOptions: {
          password: true,
        },
        displayOptions: {
          show: {
            operation: ['Authenticate'],
          },
        },
        default: '',
        description: 'The password for logging in',
        required: true,
      },
      {
        displayName: 'Project Name',
        name: 'projectName',
        type: 'string',
        displayOptions: {
          show: {
            operation: ['uploadFile'],
          },
        },
        default: '',
        description: 'Name of the project you want to create.',
      },
      {
        displayName: 'Source Language',
        name: 'sourceLanguage',
        type: 'string',
        displayOptions: {
          show: {
            operation: ['uploadFile'],
          },
        },
        default: '',
        description: 'Language the project originates from.',
      },
      {
        displayName: 'Target Language',
        name: 'targetLanguage',
        type: 'string',
        displayOptions: {
          show: {
            operation: ['uploadFile'],
          },
        },
        default: '',
        description: 'The language you wish to translate the project into.',
      },
      {
        displayName: 'File Input',
        name: 'fileInput',
        type: 'string',
        displayOptions: {
          show: {
            operation: ['uploadFile'],
          },
        },
        default: '',
        placeholder: 'Choose file...',
        typeOptions: {
          multipleValues: false,
          multipleValueButtonText: 'Add File',
        },
        required: true,
        description: 'The file that needs to be processed.',
      },
    ],
  };
}

I attempted to create two separate files called auth.node.ts and upload.node.ts and include them in the package.json, but that only resulted in two distinct core nodes as I mentioned earlier. How can I structure this correctly to achieve a single node that contains multiple actions?

classic mistake - you’ve got multiple node files when you need just one. delete the separate auth/upload files and put everything in Company.node.ts. your package.json points to two different files, so n8n thinks they’re separate nodes. just use one entry: “dist/nodes/Company.node.js”. the operation dropdown logic works fine, you just need to merge the files.

This happens when n8n loads multiple node files instead of a single consolidated file. Your code structure looks good for a multi-operation node, but the issue lies in how you’re building and registering the files. You need to consolidate everything into just one main node file with all the operation logic - instead of having separate files for each action. Remove the auth.node.ts and upload.node.ts files completely and merge their content into a single Company.node.ts file. Update your package.json to reference only this one file. Ensure that your Company class includes an execute method that handles the different operations based on the selected option. After rebuilding and restarting n8n, you should see just one Company node in the panel with a functioning operation dropdown.

Your code looks right for creating a single node with multiple operations. If you’re seeing separate nodes in the panel, it’s probably a build or file structure issue, not your node definition. Check that you’re only exporting one node class from your main file. Your package.json should only reference this single main node file - not the separate authenticate and uploadFile files. It should look like: “nodes”: { “main”: [ “dist/nodes/Company.node.js” ] }. Also make sure you’re not accidentally registering the auth.node.ts and upload.node.ts files somewhere else in your config. The structure you’ve got with the operation dropdown and displayOptions is exactly how n8n handles multi-operation nodes. Once you fix the registration issue, you should see just one “Company” node that shows the operation selector when you add it to your workflow.