from nicegui import ui from ..search import index_sizes from ..windows import bash_window, code_window, python_window from . import doc, run_documentation doc.title('Configuration & Deployment') @doc.demo('URLs', ''' You can access the list of all URLs on which the NiceGUI app is available via `app.urls`. The URLs are not available in `app.on_startup` because the server is not yet running. Instead, you can access them in a page function or register a callback with `app.urls.on_change`. ''') def urls_demo(): from nicegui import app # @ui.page('/') # def index(): # for url in app.urls: # ui.link(url, target=url) # END OF DEMO ui.link('https://nicegui.io', target='https://nicegui.io') doc.intro(run_documentation) @doc.demo('Native Mode', ''' You can enable native mode for NiceGUI by specifying `native=True` in the `ui.run` function. To customize the initial window size and display mode, use the `window_size` and `fullscreen` parameters respectively. Additionally, you can provide extra keyword arguments via `app.native.window_args` and `app.native.start_args`. Pick any parameter as it is defined by the internally used [pywebview module](https://pywebview.flowrl.com/api) for the `webview.create_window` and `webview.start` functions. Note that these keyword arguments will take precedence over the parameters defined in `ui.run`. Additionally, you can change `webview.settings` via `app.native.settings`. In native mode the `app.native.main_window` object allows you to access the underlying window. It is an async version of [`Window` from pywebview](https://pywebview.flowrl.com/api/#webview-window). Native mode requires a recent browser engine (Chromium 121+ or WebKit/Safari 17.2+). On Linux, ensure you have a modern browser engine — e.g. an up-to-date WebKitGTK or Qt-based backend. On Windows, native mode requires the .NET Framework to be installed, as pywebview uses it for the EdgeChromium backend. This is typically pre-installed on standard Windows installations, but may be missing on minimal or freshly installed systems. On Windows, a file-path `favicon` is also used as the native window icon (taskbar, title bar). The `.ico` format is required. **Port Selection:** In native mode, NiceGUI automatically finds an open port if none is specified via the `port` parameter. This is handled by `native.find_open_port()` which scans ports 8000-8999 by default. This is particularly useful when packaging your app with PyInstaller, allowing multiple copies of the same executable to run simultaneously. In browser mode, the port defaults to 8080 and is not scanned automatically — pass `port=native.find_open_port()` yourself if multiple instances should run side by side. **Storage in Native Mode:** All [storage types](/documentation/storage) work the same in native mode as in web mode. The storage files are saved to the path specified by the `NICEGUI_STORAGE_PATH` environment variable (defaults to ".nicegui" in the working directory). As with any NiceGUI app, multiple instances started from the same working directory share this path. Since each process holds its own copy of the data in memory and rewrites the storage files on change, the instances do not see each other's data and silently overwrite each other's writes. Native mode makes this situation especially likely, because multiple copies of the same packaged executable can run simultaneously. To avoid it, give each instance its own `NICEGUI_STORAGE_PATH`, or use [Redis storage](/documentation/storage#redis_storage) to share data consistently across instances. ''', tab=lambda: ui.label('NiceGUI')) def native_mode_demo(): from nicegui import app app.native.window_args['resizable'] = False app.native.start_args['debug'] = True app.native.settings['ALLOW_DOWNLOADS'] = True ui.label('app running in native mode') # ui.button('enlarge', on_click=lambda: app.native.main_window.resize(1000, 700)) # # ui.run(native=True, window_size=(400, 300), fullscreen=False) # END OF DEMO ui.button('enlarge', on_click=lambda: ui.notify('window will be set to 1000x700 in native mode')) @doc.demo('Native Window Events', ''' In native mode you can react to window lifecycle events using `app.native.on`. Handlers can be sync or async and optionally accept a `NativeEventArguments` parameter. Supported events: "shown", "loaded", "minimized", "maximized", "restored", "resized", "moved", "closed", "drop". The "resized" event provides `width` and `height` in `e.args`, "moved" provides `x` and `y`, and "drop" provides `files` with a list of filesystem paths. ''', tab=lambda: ui.label('NiceGUI')) def native_events_demo(): from nicegui import app ui.label('Try this demo in native mode to see the events in action!') # app.native.on('minimized', lambda: print('Window minimized')) # app.native.on('resized', lambda e: print(f'{e.args["width"]}x{e.args["height"]}')) # app.native.on('drop', lambda e: print(f'Dropped files: {e.args["files"]}')) # # ui.run(native=True) doc.text('', ''' Note that the native app is run in a separate [process](https://docs.python.org/3/library/multiprocessing.html#multiprocessing.Process). Therefore any configuration changes from code run under a [main guard](https://docs.python.org/3/library/__main__.html#idiomatic-usage) is ignored by the native app. The following examples show the difference between a working and a non-working configuration. For packaged apps (nicegui-pack, PyInstaller, etc.), also see the "Packaging with Native Mode" section below regarding the correct placement of `freeze_support()`. ''') @doc.ui def native_main_guard(): with ui.grid().classes('w-full grid-cols-[1fr_1fr] max-xl:grid-cols-1 gap-4 items-stretch'): python_window(''' from nicegui import app, ui app.native.window_args['resizable'] = False # works if __name__ == '__main__': ui.run(native=True, reload=False) ''', title='good_example.py') python_window(''' from nicegui import app, ui if __name__ == '__main__': app.native.window_args['resizable'] = False # ignored ui.run(native=True, reload=False) ''', title='bad_example.py') # Show a helpful workaround until issue is fixed upstream. # For more info see: https://github.com/r0x0r/pywebview/issues/1078 doc.text('', ''' If webview has trouble finding required libraries, you may get an error relating to "WebView2Loader.dll". To work around this issue, try moving the DLL file up a directory, e.g.: * from `.venv/Lib/site-packages/webview/lib/x64/WebView2Loader.dll` * to `.venv/Lib/site-packages/webview/lib/WebView2Loader.dll` ''') @doc.demo('Environment Variables', ''' You can set the following environment variables to configure NiceGUI: - `MATPLOTLIB` (default: true) can be set to `false` to avoid the potentially costly import of Matplotlib. This will make `ui.pyplot` and `ui.line_plot` unavailable. - `NICEGUI_STORAGE_PATH` (default: local ".nicegui") can be set to change the location of the storage files. - `MARKDOWN_CONTENT_CACHE_SIZE` (default: 1000): The maximum number of Markdown content snippets that are cached in memory. - `RST_CONTENT_CACHE_SIZE` (default: 1000): The maximum number of ReStructuredText content snippets that are cached in memory. - `NICEGUI_REDIS_URL` (default: None, means local file storage): The URL of the Redis server to use for shared persistent storage. - `NICEGUI_REDIS_KEY_PREFIX` (default: "nicegui:"): The prefix for Redis keys. ''') def env_var_demo(): from nicegui.elements import markdown ui.label(f'Markdown content cache size is {markdown.prepare_content.cache_info().maxsize}') @doc.demo('Custom JSON Serialization', ''' NiceGUI serializes everything it sends to the browser as JSON: element properties, event arguments, arguments of `ui.run_javascript`, and FastAPI responses. Basic Python types, dataclasses, enums, dates and NumPy arrays are supported out of the box. For other types you can register a converter with `json.register_type` that turns an object into something JSON-serializable, like a dict or a string. Converters also apply to subclasses, so one registration covers a whole class hierarchy such as Pydantic's `BaseModel`. Note that converters cannot override types which are supported natively, like dataclasses or datetimes. And the conversion is one-way: when reading from `app.storage`, you get the converted JSON structure, not the original object. *Added in version 3.19.0* ''') def custom_json_demo(): from pydantic import BaseModel from nicegui import json class Person(BaseModel): name: str age: int json.register_type(BaseModel, BaseModel.model_dump) ui.aggrid({ 'columnDefs': [{'field': 'name'}, {'field': 'age'}], 'rowData': [Person(name='Alice', age=18), Person(name='Bob', age=21)], }) @doc.demo('Background Tasks', ''' `background_tasks.create()` allows you to run an async function in the background and return a task object. By default the task will be automatically cancelled during shutdown. You can prevent this by using the `@background_tasks.await_on_shutdown` decorator (added in version 2.16.0). This is useful for tasks that need to be completed even when the app is shutting down. Exceptions in background tasks are passed to `app.on_exception` handlers. If you pass a `context` like a container element or `ui.context.client`, the task runs within this context and exceptions are also passed to the `ui.on_exception` handlers of the corresponding page (added in version 3.18.0). ''') def background_tasks_demo(): # import aiofiles import asyncio from nicegui import background_tasks results = {'answer': '?'} async def compute() -> None: await asyncio.sleep(1) results['answer'] = 42 @background_tasks.await_on_shutdown async def backup() -> None: await asyncio.sleep(1) # async with aiofiles.open('backup.json', 'w') as f: # await f.write(f'{results["answer"]}') # print('backup.json written', flush=True) ui.label().bind_text_from(results, 'answer', lambda x: f'answer: {x}') ui.button('Compute', on_click=lambda: background_tasks.create(compute())) ui.button('Backup', on_click=lambda: background_tasks.create(backup())) doc.text('Custom Vue Components', ''' You can create custom components by subclassing `ui.element` and implementing a corresponding Vue component. The ["Custom Vue components" example](https://github.com/zauberzeug/nicegui/tree/main/examples/custom_vue_component) demonstrates how to create a custom counter component which emits events and receives updates from the server. The ["Signature pad" example](https://github.com/zauberzeug/nicegui/blob/main/examples/signature_pad) and the ["Node module integration" example](https://github.com/zauberzeug/nicegui/blob/main/examples/node_module_integration) demonstrate how to bundle a custom Vue component with its dependencies defined in a `package.json` file. In Python we can use the `esm` parameter when subclassing `ui.element` to specify the ESM module name and the path to the bundled component. This adds the ESM module to the import map of the page and makes it available in the Vue component. ''') doc.text('Server Hosting', ''' To deploy your NiceGUI app on a server, you will need to execute your `main.py` (or whichever file contains your `ui.run(...)`) on your cloud infrastructure. You can, for example, just install the [NiceGUI python package via pip](https://pypi.org/project/nicegui/) and use systemd or similar service to start the main script. In most cases, you will set the port to 80 (or 443 if you want to use HTTPS) with the `ui.run` command to make it easily accessible from the outside. A convenient alternative is the use of our [pre-built multi-arch Docker image](https://hub.docker.com/r/zauberzeug/nicegui) which contains all necessary dependencies. With this command you can launch the script `main.py` in the current directory on the public port 80: ''') @doc.ui def docker_run(): bash_window(''' docker run -it --restart always \\ -p 80:8080 \\ -e PUID=$(id -u) \\ -e PGID=$(id -g) \\ -v $(pwd)/:/app/ \\ zauberzeug/nicegui:latest ''').classes('w-full') doc.text('', ''' The demo assumes `main.py` uses the port 8080 in the `ui.run` command (which is the default). The `-d` tells docker to run in background and `--restart always` makes sure the container is restarted if the app crashes or the server reboots. Of course this can also be written in a Docker compose file: ''') @doc.ui def docker_compose(): code_window(''' app: image: zauberzeug/nicegui:latest restart: always ports: - 80:8080 environment: - PUID=1000 # change this to your user id - PGID=1000 # change this to your group id volumes: - ./:/app/ ''', title='docker-compose.yml', language='yaml').classes('w-full') doc.text('', ''' There are other handy features in the Docker image like non-root user execution and signal pass-through. For more details we recommend to have a look at our [Docker example](https://github.com/zauberzeug/nicegui/tree/main/examples/docker_image). To serve your application with [HTTPS](https://fastapi.tiangolo.com/deployment/https/) encryption, you can provide SSL certificates in multiple ways. For instance, you can directly provide your certificates to [Uvicorn](https://www.uvicorn.org/), which NiceGUI is based on, by passing the relevant [options](https://www.uvicorn.org/#command-line-options) to `ui.run()`. If both a certificate and key file are provided, the application will automatically be served over HTTPS: ''') @doc.ui def uvicorn_ssl(): python_window(''' from nicegui import ui ui.run( port=443, ssl_certfile="", ssl_keyfile="", ) ''').classes('w-full') doc.text('', ''' In production we also like using reverse proxies like [Traefik](https://doc.traefik.io/traefik/) or [NGINX](https://www.nginx.com/) to handle these details for us. See our development [docker-compose.yml](https://github.com/zauberzeug/nicegui/blob/main/docker-compose.yml) as an example based on traefik or [this example nginx.conf file](https://github.com/zauberzeug/nicegui/blob/main/examples/nginx_https/nginx.conf) showing how NGINX can be used to handle the SSL certificates and reverse proxy to your NiceGUI app. You may also have a look at [our demo for using a custom FastAPI app](https://github.com/zauberzeug/nicegui/tree/main/examples/fastapi). This will allow you to do very flexible deployments as described in the [FastAPI documentation](https://fastapi.tiangolo.com/deployment/). Note that there are additional steps required to allow multiple workers. ''') doc.text('Package for Installation', ''' NiceGUI apps can also be bundled into an executable with `nicegui-pack` which is based on [PyInstaller](https://www.pyinstaller.org/). This allows you to distribute your app as a single file that can be executed on any computer. Just make sure - to call `ui.run` with `reload=False` in your main script to disable the auto-reload feature, and - to pass a `root` page function to `ui.run` or define at least one decorated `@page` function. Running the `nicegui-pack` command below will create an executable `myapp` in the `dist` folder: ''') @doc.ui def pyinstaller(): with ui.grid().classes('w-full grid-cols-[1fr_1fr] max-xl:grid-cols-1 gap-4 items-stretch'): python_window(''' from nicegui import native, ui def root(): ui.label('Hello from PyInstaller') ui.run(root, reload=False, port=native.find_open_port()) ''') bash_window(''' nicegui-pack --onefile --name "myapp" main.py ''') doc.text('', ''' **Packaging Tips:** - When building a PyInstaller app, your main script can use a native window (rather than a browser window) by using `ui.run(reload=False, native=True)`. The `native` parameter can be `True` or `False` depending on whether you want a native window or to launch a page in the user's browser - either will work in the PyInstaller generated app. - Specifying `--windowed` to `nicegui-pack` will prevent a terminal console from appearing. However you should only use this option if you have also specified `native=True` in your `ui.run` command. Without a terminal console the user won't be able to exit the app by pressing Ctrl-C. With the `native=True` option, the app will automatically close when the window is closed, as expected. - Specifying `--windowed` to `nicegui-pack` will create an `.app` file on Mac which may be more convenient to distribute. When you double-click the app to run it, it will not show any console output. You can also run the app from the command line with `./myapp.app/Contents/MacOS/myapp` to see the console output. - Specifying `--onefile` to `nicegui-pack` will create a single executable file. Whilst convenient for distribution, it will be slower to start up. This is not NiceGUI's fault but just the way Pyinstaller zips things into a single file, then unzips everything into a temporary directory before running. You can mitigate this by removing `--onefile` from the `nicegui-pack` command, and zip up the generated `dist` directory yourself, distribute it, and your end users can unzip once and be good to go, without the constant expansion of files due to the `--onefile` flag. - Specifying `--onedir` to `nicegui-pack` will create an executable with all supporting files in a directory. This starts faster than "--onefile" because it skips the unpacking step. For distribution, package the directory into an archive file (e.g., .zip or .7z). - Specifying `--clean` to `nicegui-pack` will clean the PyInstaller cache (in `./build` folder) and remove temporary files before building. - Specifying `--noconfirm` to `nicegui-pack` will replace the output directory (`./dist/SPECNAME`) without asking for confirmation. - Summary of user experience for different options: | `nicegui-pack` | `ui.run(...)` | Explanation | | :--- | :--- | :--- | | `onefile` | `native=False` | Single executable generated in `dist/`, runs in browser | | `onefile` | `native=True` | Single executable generated in `dist/`, runs in popup window | | `onefile` and `windowed` | `native=True` | Single executable generated in `dist/` (on Mac a proper `dist/myapp.app` generated incl. icon), runs in popup window, no console appears | | `onefile` and `windowed` | `native=False` | Avoid (no way to exit the app) | | Specify neither | | A `dist/myapp` directory created which can be zipped manually and distributed; run with `dist/myapp/myapp` | - If you are using a Python virtual environment, ensure you `pip install pyinstaller` within your virtual environment so that the correct PyInstaller is used, or you may get broken apps due to the wrong version of PyInstaller being picked up. That is why the `nicegui-pack` invokes PyInstaller using `python -m PyInstaller` rather than just `pyinstaller`. ''') @doc.ui def install_pyinstaller(): bash_window(''' python -m venv venv source venv/bin/activate pip install nicegui pip install pyinstaller ''').classes('w-full') doc.text('', ''' Note: If you're getting an error "TypeError: a bytes-like object is required, not 'str'", try adding the following lines to the top of your `main.py` file: ```py import sys sys.stdout = open('logs.txt', 'w') ``` See for more information. ''') doc.text('Packaging with Nuitka', ''' NiceGUI apps can also be bundled with [Nuitka](https://nuitka.net/), which compiles Python to C. Compared to PyInstaller, builds take longer but the resulting binaries are harder to decompile. Two flags are required because NiceGUI uses [PEP 562](https://peps.python.org/pep-0562/) lazy imports that Nuitka's static analyzer cannot follow on its own: - `--include-package=nicegui` bundles every submodule reachable through `from nicegui import ui`. - `--include-package-data=nicegui` bundles data files (templates, libraries, ESM bundles for elements). The same `ui.run` rules as for PyInstaller apply: call it with `reload=False` and provide a `root` page or at least one `@page` function. ''') @doc.ui def nuitka(): with ui.grid().classes('w-full grid-cols-[1fr_1fr] max-xl:grid-cols-1 gap-4 items-stretch'): python_window(''' from nicegui import native, ui def root(): ui.label('Hello from Nuitka') ui.run(root, reload=False, port=native.find_open_port()) ''') bash_window(''' python -m nuitka \\ --onefile \\ --include-package=nicegui \\ --include-package-data=nicegui \\ main.py ''') doc.text('', ''' **Tips:** - Use `--standalone` for a `main.dist/` directory that starts faster than `--onefile`, which unpacks itself into a temporary directory on every launch. - For optional packages your app uses (e.g. `pyecharts` for `ui.echart.from_pyecharts`, or any other third-party package shipping templates or data files), add matching `--include-package=` and `--include-package-data=` flags. - Native mode (`ui.run(reload=False, native=True)`) works the same as with PyInstaller. Platform-specific flags include `--macos-create-app-bundle` (Mac), `--windows-disable-console` (Windows), and `--linux-onefile-icon=` (Linux). - First builds are slow because Nuitka compiles the entire dependency graph; subsequent builds reuse Nuitka's cache. Add `--show-progress` to monitor long builds. ''') doc.text('', ''' **Packaging with Native Mode** When packaging your app (using nicegui-pack, PyInstaller, py2exe, etc.), you need to call `freeze_support()` to prevent new processes from being spawned in an endless loop. It should be called as the first statement inside the main guard. If you use `app.native` settings, they must be defined **outside** the main guard so they are applied before `freeze_support()` intercepts the subprocess: ```python from multiprocessing import freeze_support from nicegui import app, ui app.native.window_args['transparent'] = True # outside main guard # any other code (page functions, etc.) if __name__ == '__main__': freeze_support() # first statement in main guard ui.run(native=True, reload=False) ``` ''') doc.text('Documentation Index', ''' NiceGUI serves its entire documentation as machine-readable JSON endpoints. Each index is a JSON array of objects with these fields: | Field | Type | Description | | --------- | ------- | ------------------------------------------------------------------------------------------- | | `title` | string | Section heading, e.g. "Button: Click Handler" or "Example: Chat App" | | `content` | string | Description or search text (Markdown or reStructuredText) | | `format` | string | Content format: "md" or "rst" | | `url` | string | Doc page path or GitHub example link | | `demo` | string? | Complete Python demo code, or "" if none (sitewide index only; key absent in other indices) | ''') @doc.ui def _documentation_index_table(): ui.markdown(f''' **Available indices:** | Endpoint | Entries | Tokens | Includes code | Use case | | -------- | ------- | ------ | ------------- | -------- | | [`/static/sitewide_index.json`](https://nicegui.io/static/sitewide_index.json) | {index_sizes['sitewide']['entries']} | ~{index_sizes['sitewide']['tokens']}k | Yes | RAG, AI tooling, full context | | [`/static/search_index.json`](https://nicegui.io/static/search_index.json) | {index_sizes['search']['entries']} | ~{index_sizes['search']['tokens']}k | No | Powers the on-site doc search; includes GitHub examples | | [`/static/examples_index.json`](https://nicegui.io/static/examples_index.json) | {index_sizes['examples']['entries']} | ~{index_sizes['examples']['tokens']}k | No | [GitHub examples](https://github.com/zauberzeug/nicegui/tree/main/examples) only | ''') doc.text('', ''' We welcome contributions for MCP servers, AI agent skills, and enhanced RAG implementations — see the [contributing guide](https://github.com/zauberzeug/nicegui/blob/main/CONTRIBUTING.md). ''') doc.text('NiceGUI On Air', ''' By using `ui.run(on_air=True)` you can share your local app with others over the internet 🧞. When accessing the on-air URL, all libraries (like Vue, Quasar, ...) are loaded from our CDN. Thereby only the raw content and events need to be transmitted by your local app. This makes it blazing fast even if your app only has a poor internet connection (e.g. a mobile robot in the field). By setting `on_air=True` you will get a random URL which is valid for 1 hour. If you sign-up at , you can setup an organization and device name to get a fixed URL: `https://on-air.nicegui.io//`. The device is then identified by a unique, private token which you can use instead of a boolean flag: `ui.run(on_air='')`. If you [sponsor us](https://github.com/sponsors/zauberzeug), we will enable multi-device management and provide built-in passphrase protection for each device. Currently On Air is available as a tech preview and can be used free of charge. We will gradually improve stability and extend the service with usage statistics, remote terminal access and more. Please let us know your feedback on [GitHub](https://github.com/zauberzeug/nicegui/discussions), [Reddit](https://www.reddit.com/r/nicegui/), or [Discord](https://discord.gg/TEpFeAaF4f). **Data Privacy:** We take your privacy very serious. NiceGUI On Air does not log or store any content of the relayed data. ''')