Complete configuration for Frappe app packaging in v14 (setup.py) and v15 (pyproject.toml).
[build-system]
requires = ["flit_core >=3.4,<4"]
build-backend = "flit_core.buildapi"
[project]
name = "my_custom_app"
authors = [
{ name = "Your Company", email = "developers@example.com" }
]
description = "Description of your custom app"
requires-python = ">=3.10"
readme = "README.md"
dynamic = ["version"]
dependencies = [][build-system]
requires = ["flit_core >=3.4,<4"]
build-backend = "flit_core.buildapi"
[project]
name = "my_custom_app"
authors = [
{ name = "Your Company", email = "developers@example.com" }
]
description = "Description of your custom app"
requires-python = ">=3.10"
readme = "README.md"
dynamic = ["version"]
license = "MIT"
keywords = ["frappe", "erpnext", "custom-app"]
# Python package dependencies (NOT Frappe/ERPNext!)
dependencies = [
"requests~=2.31.0",
"pandas~=2.0.0",
]
classifiers = [
"Development Status :: 4 - Beta",
"Framework :: Frappe",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3.10",
]
[project.urls]
Homepage = "https://example.com"
Repository = "https://github.com/your-org/my_custom_app.git"
"Bug Reports" = "https://github.com/your-org/my_custom_app/issues"
# Frappe app dependencies (bench manages these)
[tool.bench.frappe-dependencies]
frappe = ">=15.0.0,<16.0.0"
erpnext = ">=15.0.0,<16.0.0"
# APT dependencies for Frappe Cloud
[deploy.dependencies.apt]
packages = ["libmagic1", "ffmpeg"]
# Ruff linter configuration
[tool.ruff]
line-length = 110
target-version = "py310"
[tool.ruff.lint]
select = ["E", "F", "B"]
[tool.ruff.lint.isort]
known-first-party = ["frappe", "erpnext", "my_custom_app"][build-system]
requires = ["flit_core >=3.4,<4"]
build-backend = "flit_core.buildapi"| Field | Value | Explanation |
|---|---|---|
requires |
["flit_core >=3.4,<4"] |
Frappe standard build tool |
build-backend |
"flit_core.buildapi" |
Flit reads __version__ from __init__.py |
CRITICAL: Flit requires dynamic = ["version"] in [project] section.
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | ✅ | Package name (MUST match directory) |
authors |
list | ✅ | Author(s) with name and email |
description |
string | ✅ | Short description |
requires-python |
string | ✅ | Python version (>=3.10 for v14/v15) |
readme |
string | Recommended | Path to README file |
dynamic |
list | ✅ | ALWAYS ["version"] for flit |
dependencies |
list | Optional | Python package dependencies |
license |
string | Optional | SPDX license identifier |
keywords |
list | Optional | Keywords |
classifiers |
list | Optional | PyPI classifiers |
dependencies = [
# Exact version
"requests==2.31.0",
# Compatible version (2.31.x)
"requests~=2.31.0",
# Minimum version
"requests>=2.31.0",
# Version range
"requests>=2.28.0,<3.0.0",
# No version restriction (AVOID)
"requests",
]CRITICAL: Frappe/ERPNext dependencies do NOT go in dependencies!
[tool.bench.frappe-dependencies]
frappe = ">=15.0.0,<16.0.0"
erpnext = ">=15.0.0,<16.0.0"
hrms = ">=15.0.0,<16.0.0"These are checked by bench get-app, not by pip.
[deploy.dependencies.apt]
packages = [
"libmagic1",
"ffmpeg",
"wkhtmltopdf"
]For Frappe Cloud deployments - installs system packages.
from setuptools import setup, find_packages
setup(
name="my_custom_app",
version="0.0.1",
description="Description of your custom app",
author="Your Company",
author_email="developers@example.com",
packages=find_packages(),
zip_safe=False,
include_package_data=True,
install_requires=[],
)from setuptools import setup, find_packages
with open("requirements.txt") as f:
install_requires = f.read().strip().split("\n")
with open("README.md") as f:
long_description = f.read()
setup(
name="my_custom_app",
version="0.0.1",
description="Description of your custom app",
long_description=long_description,
long_description_content_type="text/markdown",
author="Your Company",
author_email="developers@example.com",
packages=find_packages(),
zip_safe=False,
include_package_data=True,
install_requires=install_requires,
python_requires=">=3.10",
classifiers=[
"Development Status :: 4 - Beta",
"Framework :: Frappe",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3.10",
],
)requests~=2.31.0
pandas>=2.0.0
python-dateutil>=2.8.0
pytest>=7.0.0
black>=23.0.0
ruff>=0.0.280
Note: With developer_mode=True, dev-requirements are also installed.
# my_custom_app/__init__.py
__version__ = "0.0.1"CRITICAL: The __version__ variable is REQUIRED. Flit reads this automatically.
# my_custom_app/__init__.py
"""My Custom App - A brief description."""
__version__ = "0.0.1"
__title__ = "My Custom App"
__author__ = "Your Company"
__license__ = "MIT"| Format | Example | Usage |
|---|---|---|
| Major.Minor.Patch | 1.2.3 |
Stable releases |
| Major.Minor.Patch-dev | 1.2.3-dev |
Development versions |
| Major.x.x-develop | 15.x.x-develop |
Branch versions (ERPNext style) |
| Frappe Version | Python Minimum |
|---|---|
| v14 | >=3.10 |
| v15 | >=3.10 |
| v16 | >=3.14 |
- Create pyproject.toml with correct structure
- Move dependencies from requirements.txt to pyproject.toml
- Verify
__version__in__init__.py - Remove (optional): setup.py, MANIFEST.in, requirements.txt
- Test with
bench get-appandbench install-app
- Package name in pyproject.toml MUST match directory name
- Add
dynamic = ["version"]for flit - Define
__version__in__init__.py - Frappe dependencies in
[tool.bench.frappe-dependencies]
- Put Frappe/ERPNext in project dependencies (not on PyPI)
- Forget
__version__(flit build fails) - Use build-backend other than flit_core