> ## Documentation Index
> Fetch the complete documentation index at: https://docs.noxus.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 5. Working with Files

> Read and create files from plugin nodes

<Note>
  This is **part 5** of the [Your First Plugin](/developers/plugins/your-first-plugin) tutorial. Make sure you've completed [4. Use Integration in Node](/developers/plugins/tutorial/using-integrations) first.
</Note>

Plugins can read and create files. Since plugins run in isolated processes, file I/O goes through the platform's file helper — the SDK handles all the bridging transparently.

## Reading files

Add a node that reads a file input:

```python theme={null}
from noxus_sdk.files import File


class ParseWeatherFileConfig(NodeConfiguration):
    pass


class ParseWeatherFileNode(BaseNode[ParseWeatherFileConfig]):
    node_name = "parse_weather_file"
    title = "Parse Weather File"
    description = "Reads a CSV file of cities and returns weather data"
    category = NodeCategory.DATA
    color = "#4A90E2"

    inputs = [
        Connector(
            name="file",
            label="Cities File",
            definition=TypeDefinition(data_type=DataType.File),
        )
    ]

    outputs = [
        Connector(
            name="cities",
            label="Cities",
            definition=TypeDefinition(data_type=DataType.str, is_list=True),
        )
    ]

    async def call(self, ctx: RemoteExecutionContext, file: File) -> dict:
        # Read file content through the file helper
        content = await file.get_content(ctx)
        text = content.decode("utf-8")

        # Parse CSV lines
        cities = [line.strip() for line in text.splitlines() if line.strip()]

        return {"cities": cities}
```

### How file reading works

When a `File` type input arrives, it contains metadata (name, URI, content type) but not the actual bytes. Calling `file.get_content(ctx)` triggers:

```mermaid theme={null}
sequenceDiagram
    participant N as Node Code
    participant SDK as File Helper
    participant PS as Plugin Server
    participant P as Platform Storage

    N->>SDK: file.get_content(ctx)
    SDK->>PS: GET /files/{file_id}
    PS->>P: GET /plugin-server/files/{file_id}
    P-->>PS: File bytes
    PS-->>SDK: File bytes
    SDK-->>N: bytes
```

You can also access file metadata without downloading:

```python theme={null}
file.name          # "cities.csv"
file.content_type  # "text/csv"
file.uri           # "spot://..."
file.id            # UUID string
```

## Creating files

```python theme={null}
class GenerateReportNode(BaseNode[NodeConfiguration]):
    node_name = "generate_weather_report"
    title = "Generate Weather Report"
    description = "Creates a text file with weather data"
    category = NodeCategory.DATA
    color = "#4A90E2"

    inputs = [
        Connector(
            name="report_text",
            label="Report Text",
            definition=TypeDefinition(data_type=DataType.str),
        )
    ]

    outputs = [
        Connector(
            name="report_file",
            label="Report File",
            definition=TypeDefinition(data_type=DataType.File),
        )
    ]

    async def call(self, ctx: RemoteExecutionContext, report_text: str) -> dict:
        # Create a file through the file helper
        report_file = await File.from_bytes(
            ctx,
            data=report_text.encode("utf-8"),
            name="weather_report.txt",
            content_type="text/plain",
        )

        return {"report_file": report_file}
```

`File.from_bytes()` uploads the content to the platform's storage and returns a `File` object that downstream nodes can use.

## Quick reference

| Operation           | Code                                                                                               |
| ------------------- | -------------------------------------------------------------------------------------------------- |
| Read file content   | `content = await file.get_content(ctx)`                                                            |
| Create from bytes   | `await File.from_bytes(ctx, data=b"...", name="file.txt")`                                         |
| Create from text    | `await File.from_bytes(ctx, data=text.encode("utf-8"), name="out.txt", content_type="text/plain")` |
| Access file name    | `file.name`                                                                                        |
| Access content type | `file.content_type`                                                                                |
| Access file URI     | `file.uri`                                                                                         |
| File type input     | `TypeDefinition(data_type=DataType.File)`                                                          |
| File list input     | `TypeDefinition(data_type=DataType.File, is_list=True)`                                            |
| Image type input    | `TypeDefinition(data_type=DataType.Image)`                                                         |

## Handling multiple files

Use list types to receive or produce multiple files:

```python theme={null}
inputs = [
    Connector(
        name="files",
        label="Input Files",
        definition=TypeDefinition(data_type=DataType.File, is_list=True),
    )
]

async def call(self, ctx: RemoteExecutionContext, files: list[File]) -> dict:
    all_text = []
    for f in files:
        content = await f.get_content(ctx)
        all_text.append(content.decode("utf-8"))

    combined = "\n---\n".join(all_text)
    return {"combined_text": combined}
```

<Card title="Next: Advanced Techniques →" icon="arrow-right" href="/developers/plugins/tutorial/advanced-techniques">
  Config UI controls, dynamic config, list handling, error handling, and deployment.
</Card>
