01 / BEFORE THE CODE
Set the conditions before you run.
Running Resolve Studio
The primary route is external local Python with desktop Studio. Blackmagic’s 21.1 announcement assigns Python scripting to Studio; past success in Free does not establish current compatibility. Do not use a Fusion connection as an edition workaround.
Matching Python and installed library
The examples use Python 3.8+ syntax. Separately, use a compatible 64-bit Python interpreter and the native library from the same Resolve installation. Check the installed README for Python version and architecture requirements. “Python 3 installed” is not sufficient binary-compatibility evidence.
Local access, not network access
For an external script, check Preferences → System → General → External scripting using → Local where available in your version. These same-machine examples do not need Network. Running as administrator is not a general-purpose fix.
Local Disk Project Library
This tutorial deliberately excludes PostgreSQL/Cloud. Choose the local library and folder manually. This limits the example, not the Resolve API as a whole.
Save and close the existing project
Before applying, save and close any open project yourself, leaving Project Manager available. The script will not save, close or replace it for you. Stop if the API still reports an open project.
Current Studio edition guidance and the installed scripting documentation take precedence over older console or Free-edition tutorials.[1][4][3]
02 / API CONTRACT
Which object. Which method. Which result.
GetCurrentDatabase()- Input
- No arguments.
- Returns
- Current library information.
- Check
- This workflow accepts only DbType=Disk with a non-empty DbName.
GetCurrentFolder() / GetProjectListInCurrentFolder()- Input
- No arguments.
- Returns
- Current folder and project-name list.
- Check
- Do not turn a read failure into an empty folder; names belong to the selected context.
CreateProject(name)- Input
- New project name, not a filesystem path.
- Returns
- Project on success; the documented duplicate-name result is empty.
- Check
- One call only. No LoadProject or retry after an empty result.
GetName() / GetUniqueId()- Input
- No arguments.
- Returns
- Name and ID of the created object.
- Check
- Match it to the current project before saving.
SaveProject()- Input
- No arguments; saves the current project.
- Returns
- Boolean save acknowledgement.
- Check
- Call only after identity checks; failure does not imply a CreateProject rollback.
Resolve method contracts are documented by Blackmagic; Python and author helpers are labelled separately. The defensive checks are our example design. A non-empty object is not proof of full API compatibility.[3]
03 / UNDERSTAND
What is created, and where
CreateProject creates a project in Project Manager’s context. API_Demo_Project is not a path, source-media folder or exported .drp filename. Check the local Project Library and selected folder before writing.
The 21.0.4 README also documents an optional mediaLocationPath for CreateProject. That concerns the project’s media location, not selection of the Project Library. This introductory example uses the one-argument call because the extra argument is not needed for its purpose.
The code is limited to a local Disk library and selected folder so the first creation exercise does not also become server/cloud-library administration. That is a boundary of our code, not a claim that Resolve cannot use other libraries.[3]
04 / UNDERSTAND
Inspect the plan first
Without --apply, every operation is a read. The output shows library, db_type, folder, name_available and project_open. A plan can still print when the name is taken or a project is open: it records observations, not permission to create.
The plan does not reserve a name or lock Project Manager. Save and close the open project yourself before applying. Inspect the library and exact folder string. Root can be an empty string; use the actual reported value rather than inventing a label such as Root.
Our example accepts 1–96 characters without outer whitespace, control characters or path separators. That is a tutorial constraint, not an official Resolve project-name length limit.[3]
05 / UNDERSTAND
Applying is a separate command
Creation requires --apply, --expect-library and --expect-folder. The script re-reads the state and compares it with the expected target. An open project, duplicate name or changed folder stops execution before CreateProject.
Do not run a second copy of the script or change the library/folder in the UI concurrently. Re-reading reduces some targeting mistakes but does not create an atomic transaction or prevent another process acting between the guard and the write.
After CreateProject succeeds, compare the returned name and ID to the current project. Only then call SaveProject. It saves whichever project is current, so target identity matters more than merely getting a non-empty creation result.[3]
06 / UNDERSTAND
Why this is not “create or load”
The earlier CreateProject → LoadProject-on-failure pattern merges two different intentions. Creation failure does not prove that an existing same-name project is the intended target. Continuing into import, deletion or rendering after that fallback is particularly risky.
This example neither reuses a project nor silently adds a numeric suffix. Re-running with the same name must stop or report name_available=false in the plan. An “open a known project” workflow needs its own explicit mode and identity checks.[3]
07 / UNDERSTAND
Handle partial and uncertain results
After sending CreateProject, a missing object or lost response is not proof of rollback. Inspect Project Manager, the project list and the current project. The script never retries creation or deletes anything as automatic recovery.
When a project object exists but SaveProject did not acknowledge success, the project may already be present. Keep the observation and inspect it manually. Do not rerun the whole file with a new name before understanding the first result.
created_and_save_acknowledged means exactly that creation returned an object and the API acknowledged SaveProject after identity checks. Persistence, reopening and backups require separate inspection and are not implied by that status.[3]
PYTHON / COMPLETE EXAMPLE
Read the code. Then run it deliberately.
Download create_project.py and resolve_api_connection.py, or use the five-script bundle. The helper is our code; the official DaVinciResolveScript module and native library must come from your Resolve installation.
Download all examples + instructions (.zip) ↓"""Plan by default. Create ONE project in a local Disk library only after --apply.
Close/save any existing project manually first. Do not interact with Project
Manager while this script runs. There is no atomic transaction or automatic rollback.
"""
import argparse
import json
from resolve_api_connection import connect, project_manager
class CreationOutcomeError(RuntimeError):
"""A write may have happened. Inspect Resolve; never retry this blindly."""
def validate_name(name):
# These are limits of this tutorial, not documented Resolve name limits.
if not isinstance(name, str) or not name or name != name.strip() or len(name) > 96:
raise ValueError("Use 1-96 characters without outer whitespace")
if any(ord(ch) < 32 or ch in '/\\' for ch in name):
raise ValueError("Control characters and path separators are not accepted here")
return name
def inspect_target(manager, name):
name = validate_name(name)
database = manager.GetCurrentDatabase()
if not isinstance(database, dict) or database.get("DbType") != "Disk" or not database.get("DbName"):
raise RuntimeError("This tutorial targets a local Disk Project Library only")
folder = manager.GetCurrentFolder()
names = manager.GetProjectListInCurrentFolder()
if not isinstance(folder, str) or not isinstance(names, list) or not all(isinstance(x, str) for x in names):
raise RuntimeError("Could not read the current Project Manager folder")
return {
"library": database["DbName"],
"db_type": database["DbType"],
"folder": folder,
"name": name,
"name_available": name not in names,
"project_open": bool(manager.GetCurrentProject()),
}
def create_once(manager, name, apply=False, expected_library=None, expected_folder=None):
before = inspect_target(manager, name)
if not apply:
return {"status": "plan_only", "writes": 0, "target": before}
if expected_library is None or expected_folder is None:
raise ValueError("--apply requires --expect-library and --expect-folder, copied from the inspected target")
if before["library"] != expected_library or before["folder"] != expected_folder:
raise RuntimeError("Project Library/folder differs from the explicitly expected target")
if before["project_open"]:
raise RuntimeError("An existing project is open. Save/close it yourself; this script will not do so.")
if not before["name_available"]:
raise RuntimeError("Name already exists. No LoadProject fallback is permitted.")
# Re-read immediately before writing. This is a guard, not a lock.
if inspect_target(manager, name) != before:
raise RuntimeError("Project Manager state changed. No creation was attempted.")
try:
project = manager.CreateProject(name)
except Exception as exc:
raise CreationOutcomeError("CreateProject outcome unknown. Inspect Project Manager before any retry.") from exc
if not project:
raise CreationOutcomeError("CreateProject returned no object. No fallback, retry or deletion was attempted.")
try:
current = manager.GetCurrentProject()
project_id = project.GetUniqueId()
if not project_id or not current or current.GetUniqueId() != project_id or project.GetName() != name:
raise CreationOutcomeError("Creation returned an object, but current-project identity is not confirmed")
if not manager.SaveProject():
raise CreationOutcomeError("Project was created; SaveProject did not acknowledge success. Inspect it manually.")
except CreationOutcomeError:
raise
except Exception as exc:
raise CreationOutcomeError("Creation happened; verification/save outcome is uncertain. Do not repeat creation.") from exc
return {"status": "created_and_save_acknowledged", "target": before, "project_id": project_id}
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("name", help="New project name; not a filesystem path")
parser.add_argument("--apply", action="store_true", help="Allow one creation and save after all guards pass")
parser.add_argument("--expect-library")
parser.add_argument("--expect-folder", help="Exact folder string from plan; an empty string may represent root")
args = parser.parse_args(argv)
validate_name(args.name)
result = create_once(project_manager(connect()), args.name, args.apply, args.expect_library, args.expect_folder)
print(json.dumps(result, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()
Shared connection helper — inspect and download
This file only locates and loads the installed bridge. Native imports can write Python bytecode caches and can block; this is not a sandbox or a timeout mechanism. Use a trusted local installation.
"""Load the installed Blackmagic module; do not install a similarly named package.
This helper uses standard desktop installation paths or RESOLVE_SCRIPT_API.
It does not enable scripting, start Resolve, open projects or retry API calls.
Validate Python/native-library compatibility against your installed README.txt.
"""
from __future__ import annotations
import importlib.util
import os
from pathlib import Path
import struct
import sys
class ResolveConnectionError(RuntimeError):
"""An explicit environment, module, or application-connection failure."""
def scripting_root() -> Path:
configured = os.environ.get("RESOLVE_SCRIPT_API")
if configured is not None:
if not configured.strip() or configured != configured.strip():
raise ResolveConnectionError("RESOLVE_SCRIPT_API is empty or has outer whitespace")
if configured.startswith(('"', "'")) or configured.endswith(('"', "'")):
raise ResolveConnectionError("Do not store literal outer quotes in RESOLVE_SCRIPT_API")
root = Path(os.path.expandvars(configured)).expanduser()
elif sys.platform == "win32":
root = Path(os.environ.get("PROGRAMDATA", r"C:\ProgramData")) / "Blackmagic Design/DaVinci Resolve/Support/Developer/Scripting"
elif sys.platform == "darwin":
root = Path("/Library/Application Support/Blackmagic Design/DaVinci Resolve/Developer/Scripting")
elif sys.platform.startswith("linux"):
root = Path("/opt/resolve/Developer/Scripting")
else:
raise ResolveConnectionError("Set RESOLVE_SCRIPT_API for this platform/installation")
if not root.is_absolute():
raise ResolveConnectionError("RESOLVE_SCRIPT_API must be an absolute Scripting directory")
return root
def module_file() -> Path:
# RESOLVE_SCRIPT_API points to Scripting, not its Modules subdirectory.
return scripting_root() / "Modules" / "DaVinciResolveScript.py"
def load_resolve_module():
if struct.calcsize("P") * 8 != 64:
raise ResolveConnectionError("Use a compatible 64-bit Python interpreter")
source = module_file()
if not source.is_file():
raise ResolveConnectionError("Official DaVinciResolveScript.py not found at: " + str(source))
configured_lib = os.environ.get("RESOLVE_SCRIPT_LIB")
if configured_lib is not None:
lib = Path(os.path.expandvars(configured_lib)).expanduser()
if not lib.is_absolute() or not lib.is_file():
raise ResolveConnectionError("RESOLVE_SCRIPT_LIB must name the installed native library file")
name = "DaVinciResolveScript"
spec = importlib.util.spec_from_file_location(name, source)
if spec is None or spec.loader is None:
raise ResolveConnectionError("Cannot create a loader for the official module")
previous = sys.modules.get(name)
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
try:
spec.loader.exec_module(module)
# Blackmagic's wrapper can replace its sys.modules entry with a native module.
loaded = sys.modules[name]
if not callable(getattr(loaded, "scriptapp", None)):
raise ResolveConnectionError("Loaded module does not expose scriptapp")
return loaded
except Exception as exc:
if previous is None:
sys.modules.pop(name, None)
else:
sys.modules[name] = previous
raise ResolveConnectionError("Official module was found, but loading it failed") from exc
def connect():
module = load_resolve_module()
try:
resolve = module.scriptapp("Resolve")
except Exception as exc:
raise ResolveConnectionError("scriptapp failed; inspect the original exception") from exc
if not resolve:
raise ResolveConnectionError("No Resolve object: check running Studio, Local access and Python environment")
return resolve
def project_manager(resolve):
manager = resolve.GetProjectManager()
if not manager:
raise ResolveConnectionError("Resolve connected, but Project Manager is unavailable")
return manager
Launch commands
Plan only
python create_project.py "API_Demo_Project"Creates nothing. Match library/folder to Project Manager.
Apply after inspection
python create_project.py "API_Demo_Project" --apply --expect-library "Local Database" --expect-folder "API_Tests"Local Database and API_Tests are examples. Replace them with exact values from your plan and confirm the target is disposable.
What the code does
Inspect the target
Read local library, folder, name availability and whether a project is open.
Separate the plan
Without --apply, return a plan only; do not save or close someone else’s project.
Create once
After rechecking, send one CreateProject with no LoadProject fallback or hidden retry.
Verify and save
Check the current object’s ID, then obtain SaveProject acknowledgement.
A modifying call exists only in the explicitly selected --apply mode.
No automatic LoadProject, CloseProject, DeleteProject, SetCurrentDatabase or target renaming.
Inspect state after an uncertain outcome; do not repeat creation blindly.
DEBUG / OBSERVATION FIRST
An error is a starting point, not a diagnosis.
Name already exists
Possible reasonThe current folder contains this name.
Next checkStop. Do not load or delete it; choose a different name deliberately.
An existing project is open
Possible reasonThe API reports an open project.
Next checkSave and close it yourself. --apply does not authorize the script to do that automatically.
Library/folder differs
Possible reasonState does not match the explicit expected target.
Next checkInspect Project Manager and read a new plan. Do not loosen the check to accept any available object.
CreateProject outcome unknown
Possible reasonThe call was sent, but its outcome is not confirmed.
Next checkInspect the UI and library. Do not repeat creation or start cleanup.
SaveProject did not acknowledge success
Possible reasonCreation may have completed; saving is not confirmed.
Next checkInspect the project and save manually if appropriate. Save failure is not creation cancellation.
PRACTICE / YOUR OWN EVIDENCE
Controlled creation followed by a duplicate-name check
- Select a local test library and run without --apply. Match library/folder to Project Manager.
- Save and close the existing project manually. Apply using the exact expected library/folder, creating only a disposable test project.
- Check the name, save and reopen manually in Resolve. Read another plan with the same name: name_available should show it is taken, not create a second project.
Exactly one intended project is created. No hidden load, close, delete or library switch occurs. Verify the result after SaveProject on your workstation.
This is a checklist for your test, not a report of a test already performed on the website.
FAQ
Questions that matter in practice.
Does SaveProject create a .drp?
No. It saves the current project in its library. Creating a separate .drp is an export operation.[3]
Can this use PostgreSQL or Cloud?
Not this example: it explicitly refuses non-Disk libraries. That is a teaching boundary, not a description of every Project Manager capability.[3]
Can a duplicate name be opened automatically?
That is a different workflow. Here a duplicate stops execution; matching a name does not prove identity or authorize later edits.
Does --apply guarantee safety?
No. It is explicit permission inside this script, not a lock on other actors, a transaction or a replacement for a backup.
Can I simply retry an empty result?
No. A sent modifying call may have an uncertain outcome. Inspect the library and current project first.
SOURCES / VERSION BOUNDARIES
Sources, not implied certification.
The source review, offline Python tests and UI checks are different kinds of evidence. None establishes that every example works with every Studio build, operating system or Python version. The date below is the editorial review date, not a live-test certificate.
- Blackmagic Design — DaVinci Resolve Studio 21.1 release notes
8 September 2026. The release announcement assigns Python scripting to Studio. Edition requirements, not a test report for our examples.
- Blackmagic Design — Support Center
Release catalogue checked on 5 October 2026: 21.1.1 is listed. The latest release is not the minimum version of every method.
- Blackmagic Scripting README — snapshot 24 July 2026 (Resolve 21.0.4)
Public mirror of Blackmagic’s original README, maintained by X-Raym. Used for basic signatures; not official hosting and not a 21.1.1 README. Locate your installed Developer/Scripting/README.txt through Resolve’s Help/Documentation menu; exact menu nesting can vary.
- Blackmagic Design — Scripting and Automation
Official description of Studio Python/Lua scripting and integrations. Do not extend Studio capabilities to Free without checking the exact release.