---
title: "Functional API"
description: "Decorator-based remote execution with @d3function"
url: https://developer.disguise.one/plugins/designer-plugin/functional-api/
---

# Functional API

The Functional API provides fine-grained control over remote Python execution using decorators. Unlike the [Client API](/plugins/designer-plugin/client-api) which wraps entire classes, the Functional API lets you decorate individual functions and explicitly manage payloads and sessions.

## When to Use Functional API

-   Fine-grained control over execution
-   Mixing decorated functions with other code
-   Reusable functions that call each other (with `@d3function`)
-   Simple one-off scripts (with `@d3pythonscript`)

### Supported Decorators

The Functional API offers two decorators:

-   `@d3pythonscript`
-   `@d3function`

#### When to Use @d3pythonscript

-   Simple, self-contained scripts
-   Scripts that won’t be called frequently
-   Quick prototyping

#### When to Use @d3function

-   Reusable utility functions
-   Functions that call other functions
-   Performance-sensitive code (register once, call many times)
-   Organized module structure

| Feature | @d3pythonscript | @d3function |
| --- | --- | --- |
| Registration | Not needed | Required before execution |
| Payload content | Inlined function body | Function call |
| Function chaining | Not supported | Functions can call each other |
| Performance | Sends full code each time | Code registered once, calls are small |
| Best for | One-off scripts | Reusable utility functions |

* * *

## Examples

### Simple Execution with @d3pythonscript

Use `@d3pythonscript` for one-off scripts that don’t need registration:

```python
from designer_plugin.d3sdk import d3pythonscript, D3Session
from designer_plugin.pystub import *

@d3pythonscript
def rename_surface(surface_name: str, new_name: str):
    """Rename a surface in Designer."""
    surface: Screen2 = resourceManager.load(
        Path(f'objects/screen2/{surface_name}.apx'),
        Screen2)
    surface.rename(surface.path.replaceFilename(new_name))

# Execute with a session
with D3Session('localhost') as session:
    session.rpc(rename_surface.payload("surface 1", "new_name"))
```

### Retrieving Data from Designer with @d3pythonscript

```python
from designer_plugin.d3sdk import d3pythonscript, D3Session
from designer_plugin.pystub import *

@d3pythonscript
def get_surface_uid(surface_name: str) -> str:
    """Get the UID of a surface by name."""
    surface: Screen2 = resourceManager.load(
        Path(f'objects/screen2/{surface_name}.apx'),
        Screen2)
    return str(surface.uid)

# Execute methods remotely within a session
with D3Session('localhost') as session:
    uid = session.rpc(get_surface_uid.payload("surface 1"))
    print(f"Surface UID: {uid}")
```

⚠️

You cannot retrieve the object from `designer_plugin.pystub`. See [Limitation](/plugins/designer-plugin/designer-plugin#limitation).

### Adding a Layer with @d3pythonscript

```python
from designer_plugin.d3sdk import d3pythonscript, D3Session
from designer_plugin.pystub import *

@d3pythonscript
def hello_world(text: str):
    local_state: LocalState = state.localOrDirectorState()
    layer = local_state.track.addNewLayer(
        TextModule, 0, 60, f"Text - {text}"
    )
    layer.findSequence("text").sequence.setString(0, text)
    arial_font: TextFont = resourceManager.load(
        Path("objects/text/arial.apx"), TextFont
    )
    layer.findSequence('font').sequence.setResource(0, arial_font)

# Execute methods remotely within a session
with D3Session('localhost') as session:
    session.rpc(hello_world.payload("Hello world"))
```

### Function Chaining with @d3function

Use `@d3function` for reusable functions that can call each other:

```python
from designer_plugin.d3sdk import d3function, D3Session
from designer_plugin.pystub import *

@d3function("mymodule")
def get_time() -> str:
    import datetime
    return str(datetime.datetime.now())

@d3function("mymodule")
def get_surface_with_time(surface_name: str) -> dict:
    """Get surface info with timestamp."""
    surface: Screen2 = resourceManager.load(
        Path(f'objects/screen2/{surface_name}.apx'),
        Screen2)
    return {
        "uid": str(surface.uid),
        "time": get_time()  # Call another function in the same module
    }

# Execute with a session (module auto-registered)
with D3Session('localhost') as session:
    result = session.rpc(get_surface_with_time.payload("surface 1"))
    print(result)
```

### Async Execution with D3AsyncSession

```python
import asyncio
from designer_plugin.d3sdk import D3AsyncSession

async def main():
    async with D3AsyncSession('localhost') as session:
        result = await session.rpc(get_surface_with_time.payload("surface 1"))
        print(result)

asyncio.run(main())
```

### Other Examples

💡

For more task-focused examples you can drop into a plugin, see the [Python API Guides](/python-api/introduction#guides).

### Execution Flow

1.  **Code Preparation**: Your Python 3 code is converted to Python 2.7 (f-strings, type hints removed)
2.  **Payload Generation**: Arguments are serialised into a JSON payload
3.  **Module Registration** (for `@d3function`): Function code is lazily registered on Designer on the first `rpc()` call
4.  **Remote Execution**: Payload is sent to Designer via HTTP
5.  **Response Handling**: Return value is parsed and returned to your code

### Session Methods

Both `D3Session` and `D3AsyncSession` provide `rpc()` and `execute()` methods.

| Method | Returns | Use Case |
| --- | --- | --- |
| `rpc(payload)` | Return value only | Simple execution |
| `execute(payload)` | Full `PluginResponse` | Access logs, status, debugging |

* * *

## Details

### Module Registration

Modules are registered **lazily**. The session auto-registers a module the first time you execute a function from it. No manual setup is required in most cases.

For eager registration (e.g. to catch registration errors upfront), use `register_module` or `register_all_modules` explicitly:

```python
with D3Session('localhost') as session:
    session.register_module("mymodule")  # Register eagerly
```

### Import Handling

For `@d3pythonscript`, imports must be inside the function body:

```python
@d3pythonscript
def use_numpy():
    import numpy as np  # Import inside function
    return np.zeros(10).tolist()
```

### Code Transformation

Both decorators automatically convert Python 3 code to Python 2.7:

-   F-strings → `.format()` calls
-   Type hints → Removed
-   `async`/`await` → Converted to sync

The same conversion rules apply to the [Client API](/plugins/designer-plugin/client-api).

* * *

## Helper Functions

### get\_register\_payload()

Get the registration payload for a specific module.

**Signature:**

```python
def get_register_payload(module_name: str) -> RegisterPayload | None
```

**Returns:** `RegisterPayload` for the module, or `None` if not found.

### get\_all\_d3functions()

List all registered d3functions.

**Signature:**

```python
def get_all_d3functions() -> list[tuple[str, str]]
```

**Returns:** List of `(module_name, function_name)` tuples.

**Example:**

```python
from designer_plugin.d3sdk import get_all_d3functions

functions = get_all_d3functions()
# [("mymodule", "get_time"), ("mymodule", "get_surface")]
```

### get\_all\_modules()

List all module names with registered d3functions.

**Signature:**

```python
def get_all_modules() -> list[str]
```

**Returns:** List of module names.

**Example:**

```python
from designer_plugin.d3sdk import get_all_modules

modules = get_all_modules()
# ["mymodule", "utilities"]
```

* * *

## Next Steps

-   [Client API Reference](/plugins/designer-plugin/client-api) - Class-based remote execution
-   [API Reference](/plugins/designer-plugin/reference) - Data models and low-level API
-   [Overview](/plugins/designer-plugin/designer-plugin) - designer-plugin overview
