nextcloud-syntax-file-api
Quick Reference
Entry Points
| Interface | Namespace | How to Get |
|---|---|---|
IRootFolder |
OCP\Files\IRootFolder |
Constructor injection (DI) |
IUserFolder |
OCP\Files\Folder |
$rootFolder->getUserFolder($userId) |
ALWAYS inject IRootFolder via constructor -- NEVER instantiate it directly.
ALWAYS use getUserFolder($userId) to access a user's files -- NEVER construct paths manually to /userid/files/.
Node Hierarchy
| Interface | Namespace | Represents |
|---|---|---|
Node |
OCP\Files\Node |
Base: any filesystem entry |
File |
OCP\Files\File |
A file (extends Node) |
Folder |
OCP\Files\Folder |
A folder (extends Node) |
FileInfo |
OCP\Files\FileInfo |
Metadata-only interface |
Key Folder Methods
| Method | Return | Description |
|---|---|---|
get(string $path) |
Node |
Get node by relative path |
getById(int $id) |
Node[] |
Get nodes by file ID (returns ARRAY) |
newFile(string $path, ?string $content = null) |
File |
Create new file |
newFolder(string $path) |
Folder |
Create new folder |
nodeExists(string $path) |
bool |
Check if path exists |
getDirectoryListing() |
Node[] |
List immediate children |
search(ISearchQuery $query) |
Node[] |
Search within folder |
getById(int $id) |
Node[] |
Lookup by filecache ID |
Key File Methods
| Method | Return | Description |
|---|---|---|
getContent() |
string |
Read entire file content |
putContent(string $data) |
void |
Write/overwrite file content |
fopen(string $mode) |
resource|false |
Open file as stream |
getMimeType() |
string |
Get MIME type |
getSize() |
int|float |
Get file size in bytes |
Common Node Methods (File and Folder)
| Method | Return | Description |
|---|---|---|
getName() |
string |
Filename or folder name |
getPath() |
string |
Full internal path |
getInternalPath() |
string |
Path relative to storage |
getId() |
int |
Filecache ID |
getEtag() |
string |
ETag for caching |
getMTime() |
int |
Last modified timestamp |
getStorage() |
IStorage |
Underlying storage backend |
getParent() |
Folder |
Parent folder |
delete() |
void |
Delete this node |
move(string $targetPath) |
Node |
Move/rename node |
copy(string $targetPath) |
Node |
Copy node |
lock(int $type) |
void |
Acquire lock |
unlock(int $type) |
void |
Release lock |
File Events (OCP\Files\Events\Node\)
| Event | Trigger | Available Data |
|---|---|---|
BeforeNodeCreatedEvent |
Before file/folder creation | getNode() |
NodeCreatedEvent |
After file/folder creation | getNode() |
BeforeNodeWrittenEvent |
Before content write | getNode() |
NodeWrittenEvent |
After content write | getNode() |
BeforeNodeDeletedEvent |
Before deletion | getNode() |
NodeDeletedEvent |
After deletion | getNode() |
BeforeNodeRenamedEvent |
Before rename/move | getSource(), getTarget() |
NodeRenamedEvent |
After rename/move | getSource(), getTarget() |
BeforeNodeCopiedEvent |
Before copy | getSource(), getTarget() |
NodeCopiedEvent |
After copy | getSource(), getTarget() |
BeforeNodeTouchedEvent |
Before mtime update | getNode() |
NodeTouchedEvent |
After mtime update | getNode() |
Critical Warnings
NEVER assume getById() returns a single node -- it returns an array because a file can appear in multiple mount points (external storage, group folders). ALWAYS access $nodes[0] after checking the array is not empty.
NEVER access files without wrapping in try/catch for NotFoundException -- the file may have been deleted between check and access.
NEVER use hardcoded absolute paths like /admin/files/Documents/file.txt -- ALWAYS use getUserFolder() which returns the user's files root.
NEVER call getContent() on large files -- use fopen() with streaming for files larger than a few MB.
ALWAYS check instanceof File or instanceof Folder after get() -- the return type is Node which could be either.
ALWAYS inject IRootFolder and call getUserFolder() -- NEVER try to inject IUserFolder directly (it is not a registered DI service).
ALWAYS register file event listeners in lib/AppInfo/Application.php register() method using $context->registerEventListener().
Decision Tree: File Access
Need to access files?
├── Which user's files? → getUserFolder($userId)
│ ├── Know the path? → $userFolder->get('path/to/file')
│ │ ├── Is it a File? → instanceof \OCP\Files\File → getContent() / putContent()
│ │ └── Is it a Folder? → instanceof \OCP\Files\Folder → getDirectoryListing()
│ ├── Know the file ID? → $userFolder->getById($id)
│ │ └── ALWAYS check: if (empty($nodes)) { throw ... }
│ │ └── Use $nodes[0] for first match
│ ├── Need to create? → Does it exist?
│ │ ├── nodeExists() returns true → get() then putContent()
│ │ └── nodeExists() returns false → newFile() or newFolder()
│ └── Need to search? → $userFolder->search(ISearchQuery)
└── System-level access? → Use IRootFolder directly (admin only)
Essential Patterns
Pattern 1: Service with File Access
namespace OCA\MyApp\Service;
use OCP\Files\File;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
class FileService {
public function __construct(
private IRootFolder $rootFolder,
) {}
public function readFile(string $userId, string $path): string {
$userFolder = $this->rootFolder->getUserFolder($userId);
try {
$node = $userFolder->get($path);
if (!($node instanceof File)) {
throw new \RuntimeException('Not a file');
}
return $node->getContent();
} catch (NotFoundException $e) {
throw new NotFoundException('File not found: ' . $path);
}
}
public function writeFile(string $userId, string $path, string $content): void {
$userFolder = $this->rootFolder->getUserFolder($userId);
try {
$file = $userFolder->get($path);
if (!($file instanceof File)) {
throw new \RuntimeException('Not a file');
}
$file->putContent($content);
} catch (NotFoundException $e) {
$userFolder->newFile($path, $content);
}
}
}
Pattern 2: Safe getById() Usage
public function getFileById(string $userId, int $fileId): File {
$userFolder = $this->rootFolder->getUserFolder($userId);
$nodes = $userFolder->getById($fileId);
if (empty($nodes)) {
throw new NotFoundException('File not found for ID: ' . $fileId);
}
$node = $nodes[0];
if (!($node instanceof File)) {
throw new \RuntimeException('Node is not a file');
}
return $node;
}
Pattern 3: Folder Listing with Type Filtering
public function listFiles(string $userId, string $folderPath): array {
$userFolder = $this->rootFolder->getUserFolder($userId);
try {
$folder = $userFolder->get($folderPath);
if (!($folder instanceof Folder)) {
throw new \RuntimeException('Not a folder');
}
$result = [];
foreach ($folder->getDirectoryListing() as $node) {
$result[] = [
'name' => $node->getName(),
'type' => $node instanceof File ? 'file' : 'folder',
'size' => $node->getSize(),
'mtime' => $node->getMTime(),
'mimetype' => $node instanceof File ? $node->getMimeType() : 'httpd/unix-directory',
];
}
return $result;
} catch (NotFoundException $e) {
throw new NotFoundException('Folder not found: ' . $folderPath);
}
}
Pattern 4: File Event Listener Registration
// lib/AppInfo/Application.php
namespace OCA\MyApp\AppInfo;
use OCA\MyApp\Listener\FileCreatedListener;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\Files\Events\Node\NodeCreatedEvent;
class Application extends App implements IBootstrap {
public const APP_ID = 'myapp';
public function __construct() {
parent::__construct(self::APP_ID);
}
public function register(IRegistrationContext $context): void {
$context->registerEventListener(
NodeCreatedEvent::class,
FileCreatedListener::class
);
}
public function boot(IBootContext $context): void {}
}
// lib/Listener/FileCreatedListener.php
namespace OCA\MyApp\Listener;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Files\Events\Node\NodeCreatedEvent;
use OCP\Files\File;
use Psr\Log\LoggerInterface;
/** @template-implements IEventListener<NodeCreatedEvent> */
class FileCreatedListener implements IEventListener {
public function __construct(
private LoggerInterface $logger,
) {}
public function handle(Event $event): void {
if (!($event instanceof NodeCreatedEvent)) {
return;
}
$node = $event->getNode();
if (!($node instanceof File)) {
return;
}
$this->logger->info('File created: ' . $node->getPath());
}
}
Pattern 5: Streaming Large Files
public function streamFile(string $userId, string $path): void {
$userFolder = $this->rootFolder->getUserFolder($userId);
$file = $userFolder->get($path);
if (!($file instanceof File)) {
throw new \RuntimeException('Not a file');
}
$handle = $file->fopen('r');
if ($handle === false) {
throw new \RuntimeException('Cannot open file');
}
try {
while (!feof($handle)) {
echo fread($handle, 8192);
flush();
}
} finally {
fclose($handle);
}
}
Pattern 6: Create or Update (Upsert)
public function upsertFile(string $userId, string $path, string $content): File {
$userFolder = $this->rootFolder->getUserFolder($userId);
if ($userFolder->nodeExists($path)) {
$file = $userFolder->get($path);
if (!($file instanceof File)) {
throw new \RuntimeException('Path exists but is not a file');
}
$file->putContent($content);
return $file;
}
return $userFolder->newFile($path, $content);
}
Reference Links
- references/methods.md -- Complete IRootFolder, File, Folder, Node method tables
- references/examples.md -- File CRUD, events, storage access examples
- references/anti-patterns.md -- File handling mistakes and corrections