The IDE is not really a factor. It's the library.
libssh does support SFTP. I just installed the port; the README says
Last ported 10th February 2026, built with libssh commit ca9c055d, branch
-stable-0.11, version libssh-0.11.4.
Here's sftp.c on that branch; full of sftp_ functions. It just was not ported over to ESP32.
There may be a specific technical reason why not. But you can try doing it yourself. Download four files: right-click and Save As using the "plain" link at the end of the third-ish line that starts with "blob" on each page, just above the file content
With the four files in your Downloads directory, copy them into the existing library. For example, if your sketchbook is at ~/Arduino, then the library's src directory is at ~/Arduino/libraries/LibSSH-ESP32/src. The directory structure is a little different: the content of include is merged with src, so that the paths are
src/sftp.c (as siblings of scp.c)
src/sftp_common.c
src/libssh/sftp.h (as siblings of scp.h)
src/libssh/sftp_priv.h
At the top of sftp.c, after the comment blocks is #include "config.h" -- replace it to match the other ported .c files, with the port-specific config
#include "libssh_esp32_config.h"
Make the same change to sftp_common.c. The last line of both files
#endif /* WITH_SFTP */
indicates a conditional compilation flag. It's in the aforementioned src/libssh_esp32_config.h, at line 236
/* Define to 1 if you want to enable SFTP */
/* #undef WITH_SFTP */
so change that to
/* Define to 1 if you want to enable SFTP */
#define WITH_SFTP 1
The file is duplicated as src/libssh/libssh_esp32_config.h. You can update both, but it doesn't look like it should matter.
Now take that bit of SFTP tutorial (in the first link above) and put it in an empty sketch
#include <libssh_esp32.h>
#include <libssh/sftp.h>
int sftp_helloworld(ssh_session session) {
sftp_session sftp;
int rc;
sftp = sftp_new(session);
if (sftp == NULL)
{
Serial.printf("Error allocating SFTP session: %s\n",
ssh_get_error(session));
return SSH_ERROR;
}
rc = sftp_init(sftp);
if (rc != SSH_OK)
{
Serial.printf("Error initializing SFTP session: code %d.\n",
sftp_get_error(sftp));
sftp_free(sftp);
return rc;
}
// ...
sftp_free(sftp);
return SSH_OK;
}
void setup() {
Serial.begin(115200);
ssh_session session;
auto result = sftp_helloworld(session);
Serial.println(result);
}
void loop() {}
It compiles, but does not link successfully (reformatted for clarity):
/esp32-arduino-libs/idf-release_v5.1-632e0c2a/esp32/lib
/liblwip.a(ip6.c.obj): in function `ip6_input':
/home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/lwip/lwip/src/core/ipv6
/ip6.c:547: undefined reference to `lwip_hook_ip6_input'
Note that /home/runner is a path on the build machine that built ip6.c in LwIP that is a component of the ESP32 IDF. It's this bit
using the two arguments for the named function
Something in SFTP causes this IPv6 code to be linked in, but it is incomplete for a regular sketch. Instead of trying to figure that out and disable it, it's simpler to provide a do-nothing stub in the sketch
extern "C" int lwip_hook_ip6_input(struct pbuf *p, struct netif *inp) {
Serial.printf("hey, something is calling %s\n", __func__);
return 0;
}
That built for me. Does it work? I have no idea.