I wanted to access the sub-folders of an FTP server (created automatically by my surveillance cameras), but the node https://flows.nodered.org/node/node-red-contrib-ftp only displayed the folders root.
@afelix found a proposal to modify the original node on the Github: https://github.com/joeartsea/node-red-contrib-ftp/issues/19
You must therefore open the two files found in (RPI example): /home/pi/.node-red/node_modules/node-red-contrib-ftp
- Copy / paste in ftp.html:
<script type="text/x-red" data-template-name="ftp">
  <div class="form-row">
    <label for="node-config-input-host"><i class="fa fa-bookmark"></i> Host</label>
    <input type="text" id="node-config-input-host" placeholder="localhost" style="width: 40%;" />
    <label for="node-config-input-port" style="margin-left: 10px; width: 35px; "> Port</label>
    <input type="text" id="node-config-input-port" placeholder="21" style="width:45px">
  </div>
  <div class="form-row">
    <label for="node-config-input-user"><i class="fa fa-user"></i> User</label>
    <input type="text" id="node-config-input-user" placeholder="anonymous" />
  </div>
  <div class="form-row">
    <label for="node-config-input-password"><i class="fa fa-lock"></i> Password</label>
    <input type="password" id="node-config-input-password">
  </div>
  <div class="form-row">
    <label for="node-config-input-connTimeout"><i class="fa fa-clock-o"></i> Connection Timeout</label>
    <input type="text" id="node-config-input-connTimeout" placeholder="10000">
  </div>
  <div class="form-row">
    <label for="node-config-input-pasvTimeout"><i class="fa fa-clock-o"></i> PASV Timeout</label>
    <input type="text" id="node-config-input-pasvTimeout" placeholder="10000">
  </div>
  <div class="form-row">
    <label for="node-config-input-keepalive"><i class="fa fa-clock-o"></i> Keepalive</label>
    <input type="text" id="node-config-input-keepalive" placeholder="10000">
  </div>
  <div class="form-row">
    <label for="node-input-secure"><i class="fa fa-key"></i> Data connection encryption</label>
    <input type="checkbox" id="node-input-secure" placeholder="once" style="width: 10%; vertical-align: top;">
    <label for="node-config-input-secureOptions" style="width: 20%; vertical-align: top;"> Secure Options</label>
    <input type="text" id="node-config-input-secureOptions" style="width: 35%; vertical-align: top;">
  </div>
</script>
<script type="text/x-red" data-help-name="ftp in">
  <p>Allows LIST, GET, PUT and DELETE of files on a remote FTP server.</p>
  <p><code>msg.filename</code> is the path/name of the file on the remote FTP server</p>
  <p><code>msg.localFilename</code> is the path/name of the file on the local machine</p>
  <p>NOTE: the local machine is where NR is running - this might not be the machine where your browser connects to Node Red.</p>
  <p></p> 
  <p>GET uses <code>msg.filename</code> and <code>msg.localFilename</code></p>
  <p>PUT uses <code>msg.filename</code> and <code>msg.localFilename</code></p>
  <p>DELETE uses only <code>msg.filename</code></p>
  <p>LIST uses only <code>msg.filename</code> and returns an array of the file/folders, containing type, name, sticky, rights(an object), acl, owner, group, size and date.</p>
  <p></p>
  <p>You can pass <code>msg.filename</code> and <code>msg.localFilename</code> to this node, however values entered in the node will take precedence.</p>
  <p></p>
  <p>If an error occurs, the error message will come out as <code>msg</code>.</p>
</script>
<script type="text/javascript">
  RED.nodes.registerType('ftp', {
    category: 'config',
    color:"BurlyWood",
    defaults: {
      host: { value: '' },
      port: { value: '' },
      secure: { value: false },
      secureOptions: { value: '' },
      user: { value: '' },
      connTimeout: { value: '' },
      pasvTimeout: { value: '' },
      keepalive: { value: '' }
    },
    credentials: {
      password: { type: 'password', required: true },
    },
    label: function() {
      return this.host
    }
  });
</script>
<script type="text/x-red" data-template-name="ftp in">
  <div class="form-row">
    <label for="node-input-ftp"><i class="fa fa-user"></i> Add new FTP Server</label>
    <input type="text" id="node-input-ftp">
  </div>
  <div class="form-row">
    <label for="node-input-operation"><i class="fa fa-wrench"></i> Operation</label>
    <select type="text" id="node-input-operation">
      <option value="list">list</option>
      <option value="get">get</option>
      <option value="put">put</option>
      <option value="delete">delete</option>
    </select>
  </div>
  <div class="form-row input-filename-row hidden">
    <label for="node-input-filename"><i class="fa fa-file"></i> Filename</label>
    <input type="text" id="node-input-filename" placeholder="Filename">
  </div>
  <div class="form-row input-localFilename-row hidden">
    <label for="node-input-localFilename"><i class="fa fa-file"></i> Local Filename</label>
    <input type="text" id="node-input-localFilename" placeholder="Local Filename">
  </div>
  <div class="form-row">
    <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
    <input type="text" id="node-input-name" placeholder="Name">
  </div>
</script>
<script type="text/javascript">
  RED.nodes.registerType('ftp in', {
    category: 'storage-input',
    color:"BurlyWood",
    defaults: {
      ftp: { type: 'ftp', required: true },
      operation: { value: 'list', required: true },
      filename: { value: '' },
      localFilename: { value: '' },
      name: { value: '' }
    },
    inputs: 1,
    outputs: 1,
    icon: "file.png",
    label: function () {
      var ftpNode = RED.nodes.node(this.ftp);
      return this.name || 'ftp';
    },
    labelStyle: function () {
      return this.name ? 'node_label_italic' : '';
    },
    oneditprepare: function () {
      var filename = $(".input-filename-row");
      var localFilename = $(".input-localFilename-row");
      $("#node-input-operation").change(function () {
        var id = $("#node-input-operation option:selected").val();
        console.log(id);
        if (id == 'list') {
          filename.show();
          localFilename.hide();
        } else if (id == 'put' || id == 'get') {
          filename.show();
          localFilename.show();
        } else {
          filename.show();
          localFilename.hide();
        }
      });
    }
  });
</script>
- and in ftp.js:
module.exports = function (RED) {
  'use strict';
  var ftp = require('ftp');
  var fs = require('fs');
  function FtpNode(n) {
    RED.nodes.createNode(this, n);
    var node = this;
    var credentials = RED.nodes.getCredentials(n.id);
    this.options = {
      'host': n.host || 'localhost',
      'port': n.port || 21,
      'secure': n.secure || false,
      'secureOptions': n.secureOptions,
      'user': n.user || 'anonymous',
      'password': credentials.password || 'anonymous@',
      'connTimeout': n.connTimeout || 10000,
      'pasvTimeout': n.pasvTimeout || 10000,
      'keepalive': n.keepalive || 10000
    };
  }
  RED.nodes.registerType('ftp', FtpNode, {
    credentials: {
      password: { type: 'password' }
    }
  });
  function FtpInNode(n) {
    RED.nodes.createNode(this, n);
    this.ftp = n.ftp;
    this.operation = n.operation;
    this.filename = n.filename;
    this.localFilename = n.localFilename;
    this.ftpConfig = RED.nodes.getNode(this.ftp);
	
    if (this.ftpConfig) {
      var node = this;
      node.on('input', function (msg) {
        var conn = new ftp();
        var filename = node.filename || msg.filename || '';
        var localFilename = node.localFilename || msg.localFilename || '';
        this.sendMsg = function (err, result) {
          if (err) {
            node.error(err, msg);
            node.status({ fill: 'red', shape: 'ring', text: 'failed' });
			node.send(err);
            return;
          }
          node.status({});
          if (node.operation == 'get') {
            result.once('close', function() { conn.end(); });
            result.pipe(fs.createWriteStream(localFilename));
            msg.payload = 'Get operation successful. ' + localFilename;
          } else if (node.operation == 'put') {
            conn.end();
            msg.payload = 'Put operation successful.';
          } else {
            conn.end();
			msg.message = 'Operation successful.';
            msg.payload = result;
          }
          msg.filename = filename;
          msg.localFilename = localFilename;
          node.send(msg);
        };
        conn.on('ready', function () {
          switch (node.operation) {
            case 'list':
              conn.list(filename, node.sendMsg);
              break;
            case 'get':
              conn.get(filename, node.sendMsg);
              break;
            case 'put':
              conn.put(localFilename, filename, node.sendMsg);
              break;
            case 'delete':
              conn.delete(filename, node.sendMsg);
              break;
          }
        });
        conn.on('error', function(err) { 
          node.error(err, msg);
          node.status({ fill: 'red', shape: 'ring', text: err.message });
          node.send(err);
		  return;
        });
        conn.connect(node.ftpConfig.options);
      });
    } else {
      this.error('missing ftp configuration');
    }
  }
  RED.nodes.registerType('ftp in', FtpInNode);
}
- Restart Node-Red

This flow display files in the folder Test\newFolder
[{"id":"c00bd000.a5f0c","type":"ftp in","z":"a8b1b208.7036f","ftp":"42562df8.ff98b4","operation":"list","filename":"","localFilename":"","name":"list","x":470,"y":1600,"wires":[["a3de95d2.f1f7a8"]]},{"id":"a3de95d2.f1f7a8","type":"debug","z":"a8b1b208.7036f","name":"complet","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":640,"y":1600,"wires":[]},{"id":"b5da47bd.987978","type":"change","z":"a8b1b208.7036f","name":"pay to filename","rules":[{"t":"move","p":"payload","pt":"msg","to":"filename","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":320,"y":1600,"wires":[["c00bd000.a5f0c"]]},{"id":"19f7caab.4c1865","type":"inject","z":"a8b1b208.7036f","name":"Test\\newFolder","topic":"","payload":"Test\\newFolder","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":1600,"wires":[["b5da47bd.987978"]]},{"id":"42562df8.ff98b4","type":"ftp","z":"","host":"192.168.1.143","port":"21","secureOptions":"","user":"superninja","connTimeout":"","pasvTimeout":"","keepalive":""}]