Skip to content

Conversation

@ogabrielluiz
Copy link
Contributor

@ogabrielluiz ogabrielluiz commented Jan 20, 2026

Summary

Adds a PUT endpoint (PUT /api/v1/flows/{flow_id}) that allows creating or updating flows with a specific ID. This enables syncing flows between Langflow instances while preserving flow IDs.

Behavior:

  • If flow doesn't exist → creates with specified ID (returns 201)
  • If flow exists and user owns it → updates (returns 200)
  • If flow exists but belongs to another user → returns 403

Conflict handling:

Field CREATE UPDATE
name Auto-rename (suffix) Fail 409
endpoint_name Fail 409 Fail 409

folder_id handling:

  • CREATE without folder_id → uses default folder
  • UPDATE without folder_id → keeps existing folder
  • Invalid folder_id → returns 400

Security:

  • UUID validated by FastAPI/Pydantic
  • Ownership verified for existing flows
  • user_id always set from authenticated user (body value ignored)

Summary by CodeRabbit

  • New Features

    • Added flow upsert capability: create or update flows with a single PUT request using a specified ID
    • Automatic conflict resolution for duplicate flow names (auto-renamed with numbering)
    • Proper permission checks and appropriate HTTP status codes (201 for creation, 200 for updates, 403 for unauthorized access, 409 for conflicts)
  • Tests

    • Added comprehensive test coverage for flow upsert operations including permission validation, conflict handling, and edge cases

✏️ Tip: You can customize this high-level summary in your review settings.

@github-actions github-actions bot added the community Pull Request from an external contributor label Jan 20, 2026
@ogabrielluiz ogabrielluiz removed the community Pull Request from an external contributor label Jan 20, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 20, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

A new upsert capability for flows is introduced via a PUT endpoint that handles both creation and update operations. The implementation includes folder validation, duplicate name/endpoint detection with auto-renaming on create, ownership verification, and appropriate HTTP responses (201 for create, 200 for update, 403 for unauthorized, 409 for conflicts). A helper function manages safe updates with controlled field modifications.

Changes

Cohort / File(s) Summary
Flow Upsert API Implementation
src/backend/base/langflow/api/v1/flows.py
New upsert_flow endpoint (PUT /{flow_id}) handles creation and update with ownership checks. Enhanced _new_flow accepts flow_id, fail_on_endpoint_conflict, and validate_folder parameters, performs auto-renaming on conflicts. New _update_existing_flow helper validates and safely updates existing flows with uniqueness enforcement.
Upsert Functionality Tests
src/backend/tests/unit/api/v1/test_flows.py
Comprehensive test suite covering upsert scenarios: creation with specified ID (201), existing flow updates (200), ownership validation (403), folder validation (400), name/endpoint conflicts (409), auto-renaming behavior, folder_id preservation, user_id override, and self-conflict allowance.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API as PUT /{flow_id}
    participant DB as Database
    participant Storage as File Storage
    
    Client->>API: upsert_flow(flow_id, flow_data)
    activate API
    
    alt Flow exists
        API->>DB: Query existing flow
        DB-->>API: Return Flow
        API->>API: Verify ownership
        alt Owner mismatch
            API-->>Client: 403 Forbidden
        else Owner match
            API->>API: _update_existing_flow()
            Note over API: Validate fs_path,<br/>Check uniqueness,<br/>Exclude id/user_id
            API->>DB: Update flow record
            DB-->>API: Confirm
            API->>Storage: Save to filesystem
            Storage-->>API: Confirm
            API-->>Client: 200 OK + FlowRead
        end
    else Flow doesn't exist
        API->>API: _new_flow(flow_id,...)
        Note over API: Validate folder,<br/>Auto-rename on conflicts,<br/>Set provided flow_id
        API->>DB: Create flow record
        DB-->>API: Confirm + new ID
        API->>Storage: Save to filesystem
        Storage-->>API: Confirm
        API-->>Client: 201 Created + FlowRead
    end
    deactivate API
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~28 minutes

🚥 Pre-merge checks | ✅ 5 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Test Quality And Coverage ⚠️ Warning Tests cover primary upsert functionality but lack fs_path validation for PUT endpoint and have assertion gaps in login response handling. Add fs_path validation tests for PUT mirroring POST tests, add status code assertion after login call, add self-reference test for endpoint_name, and fix fs_path validation bug at line 611.
Test File Naming And Structure ⚠️ Warning Test file lacks assertion of login success before using authentication token in test_upsert_flow_returns_403_for_other_users_flow, violating proper setup and teardown requirements. Add assert login_response.status_code == status.HTTP_200_OK after login POST requests in multi-user tests before accessing response token.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Add PUT endpoint for flows upsert' directly and clearly summarizes the main change: introducing a new PUT endpoint for upserting flows.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Test Coverage For New Implementations ✅ Passed PR includes 246+ lines of comprehensive test coverage for all new implementations (upsert_flow, _update_existing_flow, enhanced _new_flow) with tests for happy paths, error scenarios, security checks, and edge cases.
Excessive Mock Usage Warning ✅ Passed The upsert flow test suite demonstrates excellent test design with zero mock objects, patches, or MagicMock instances across all 31 test functions, including 11 upsert-specific tests.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/add-patch-flows

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Jan 20, 2026
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Jan 20, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds a PUT endpoint for flow upsert operations, enabling flows to be created or updated with specific IDs. This feature is designed to support syncing flows between Langflow instances while preserving their identifiers.

Changes:

  • Added PUT /api/v1/flows/{flow_id} endpoint that creates flows with specified IDs or updates existing owned flows
  • Enhanced _new_flow helper to support custom IDs and configurable conflict handling
  • Added comprehensive test coverage for all upsert scenarios including ownership, conflicts, and edge cases

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated no comments.

File Description
src/backend/base/langflow/api/v1/flows.py Adds upsert_flow endpoint and _update_existing_flow helper; extends _new_flow with parameters for ID specification, endpoint conflict handling, and folder validation
src/backend/tests/unit/api/v1/test_flows.py Adds 11 comprehensive tests covering create/update paths, ownership checks, conflict handling, folder management, and security

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions
Copy link
Contributor

github-actions bot commented Jan 20, 2026

Frontend Unit Test Coverage Report

Coverage Summary

Lines Statements Branches Functions
Coverage: 18%
17.6% (5044/28644) 10.94% (2417/22084) 11.7% (734/6272)

Unit Test Results

Tests Skipped Failures Errors Time
2006 0 💤 0 ❌ 0 🔥 25.955s ⏱️

@codecov
Copy link

codecov bot commented Jan 20, 2026

Codecov Report

❌ Patch coverage is 50.00000% with 39 lines in your changes missing coverage. Please review.
✅ Project coverage is 34.59%. Comparing base (b3df3a7) to head (a4905d5).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/backend/base/langflow/api/v1/flows.py 50.00% 39 Missing ⚠️

❌ Your project status has failed because the head coverage (41.59%) is below the target coverage (60.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main   #11368      +/-   ##
==========================================
+ Coverage   34.52%   34.59%   +0.06%     
==========================================
  Files        1415     1415              
  Lines       67345    67401      +56     
  Branches     9937     9937              
==========================================
+ Hits        23254    23315      +61     
+ Misses      42861    42857       -4     
+ Partials     1230     1229       -1     
Flag Coverage Δ
backend 53.63% <50.00%> (+0.16%) ⬆️
lfx 41.59% <ø> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/backend/base/langflow/api/v1/flows.py 53.53% <50.00%> (+0.88%) ⬆️

... and 8 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Jan 20, 2026
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Jan 20, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/backend/base/langflow/api/v1/flows.py`:
- Around line 611-613: The PUT update currently skips fs_path validation when
flow.fs_path is an empty string because the condition uses truthiness; change
the guard to explicitly check for None so empty strings are validated too (i.e.,
replace the truthy check with an explicit "is not None" style check) and call
_verify_fs_path(flow.fs_path, user_id, storage_service) in that case; locate the
check around the update handler where flow.fs_path is referenced (the block
using flow.fs_path and _verify_fs_path) and make this conditional change so
empty "" values go through _verify_fs_path validation.
🧹 Nitpick comments (1)
src/backend/base/langflow/api/v1/flows.py (1)

651-666: PUT can’t explicitly clear endpoint_name (null/empty).

Line 651 drops None values, so a client-provided null won’t clear endpoint_name, and "" would persist as a real value. Consider handling explicit null/empty similar to PATCH.

Please verify the intended behavior for endpoint_name: null/"" on PUT. If clearing should be supported, a targeted override helps:

♻️ Suggested handling
-    update_data = flow.model_dump(exclude_unset=True, exclude_none=True)
+    update_data = flow.model_dump(exclude_unset=True, exclude_none=True)
+    if "endpoint_name" in flow.model_fields_set and (flow.endpoint_name is None or flow.endpoint_name == ""):
+        update_data["endpoint_name"] = None

Comment on lines 611 to 613
# Validate fs_path if provided
if flow.fs_path:
await _verify_fs_path(flow.fs_path, user_id, storage_service)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Empty fs_path skips validation in PUT updates.

Line 611 only validates when flow.fs_path is truthy, so "" bypasses _verify_fs_path and can persist an invalid path.

🐛 Proposed fix
-    if flow.fs_path:
-        await _verify_fs_path(flow.fs_path, user_id, storage_service)
+    if flow.fs_path is not None:
+        await _verify_fs_path(flow.fs_path, user_id, storage_service)
🤖 Prompt for AI Agents
In `@src/backend/base/langflow/api/v1/flows.py` around lines 611 - 613, The PUT
update currently skips fs_path validation when flow.fs_path is an empty string
because the condition uses truthiness; change the guard to explicitly check for
None so empty strings are validated too (i.e., replace the truthy check with an
explicit "is not None" style check) and call _verify_fs_path(flow.fs_path,
user_id, storage_service) in that case; locate the check around the update
handler where flow.fs_path is referenced (the block using flow.fs_path and
_verify_fs_path) and make this conditional change so empty "" values go through
_verify_fs_path validation.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/backend/base/langflow/api/v1/flows.py`:
- Around line 611-614: The PUT path currently skips validation when flow.fs_path
is an empty string because it uses a truthy check; change the condition so that
_verify_fs_path is called whenever fs_path is provided (i.e., not only truthy)
on updates—call _verify_fs_path(flow.fs_path, user_id, storage_service) when
flow.fs_path is not None (or use hasattr/explicit check in the update handler,
e.g., in the function handling flow updates) so empty-string values are
validated and rejected consistent with create behavior.

In `@src/backend/tests/unit/api/v1/test_flows.py`:
- Around line 567-571: Assert the login response succeeded before extracting the
token: after posting login_data to "api/v1/login" and storing login_response,
add an assertion that login_response.status_code (or login_response.status)
equals the expected success code (e.g., 200) and/or check login_response.json()
contains "access_token" so the subsequent creation of other_user_headers =
{"Authorization": f"Bearer {login_response.json()['access_token']}" } is safe;
update any test helper or error message to make failures explicit.

Comment on lines 611 to 614
# Validate fs_path if provided
if flow.fs_path:
await _verify_fs_path(flow.fs_path, user_id, storage_service)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Validate empty-string fs_path on PUT updates.

if flow.fs_path: skips empty strings, allowing invalid values to persist and bypassing _verify_fs_path (which already rejects ""). Consider validating whenever fs_path is provided so empty strings correctly return 400, matching create behavior.

🔧 Suggested fix
-    if flow.fs_path:
-        await _verify_fs_path(flow.fs_path, user_id, storage_service)
+    if flow.fs_path is not None:
+        await _verify_fs_path(flow.fs_path, user_id, storage_service)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Validate fs_path if provided
if flow.fs_path:
await _verify_fs_path(flow.fs_path, user_id, storage_service)
# Validate fs_path if provided
if flow.fs_path is not None:
await _verify_fs_path(flow.fs_path, user_id, storage_service)
🤖 Prompt for AI Agents
In `@src/backend/base/langflow/api/v1/flows.py` around lines 611 - 614, The PUT
path currently skips validation when flow.fs_path is an empty string because it
uses a truthy check; change the condition so that _verify_fs_path is called
whenever fs_path is provided (i.e., not only truthy) on updates—call
_verify_fs_path(flow.fs_path, user_id, storage_service) when flow.fs_path is not
None (or use hasattr/explicit check in the update handler, e.g., in the function
handling flow updates) so empty-string values are validated and rejected
consistent with create behavior.

Comment on lines 567 to 571
# Login as other user and create a flow
login_data = {"username": "other_user_for_upsert_test", "password": "testpassword"} # pragma: allowlist secret
login_response = await client.post("api/v1/login", data=login_data)
other_user_headers = {"Authorization": f"Bearer {login_response.json()['access_token']}"}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Assert login success before using the token.

If login fails, the test will raise a confusing KeyError. A clear status assertion improves signal and debugging.

🔧 Suggested fix
     login_response = await client.post("api/v1/login", data=login_data)
+    assert login_response.status_code == status.HTTP_200_OK
     other_user_headers = {"Authorization": f"Bearer {login_response.json()['access_token']}"}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Login as other user and create a flow
login_data = {"username": "other_user_for_upsert_test", "password": "testpassword"} # pragma: allowlist secret
login_response = await client.post("api/v1/login", data=login_data)
other_user_headers = {"Authorization": f"Bearer {login_response.json()['access_token']}"}
# Login as other user and create a flow
login_data = {"username": "other_user_for_upsert_test", "password": "testpassword"} # pragma: allowlist secret
login_response = await client.post("api/v1/login", data=login_data)
assert login_response.status_code == status.HTTP_200_OK
other_user_headers = {"Authorization": f"Bearer {login_response.json()['access_token']}"}
🤖 Prompt for AI Agents
In `@src/backend/tests/unit/api/v1/test_flows.py` around lines 567 - 571, Assert
the login response succeeded before extracting the token: after posting
login_data to "api/v1/login" and storing login_response, add an assertion that
login_response.status_code (or login_response.status) equals the expected
success code (e.g., 200) and/or check login_response.json() contains
"access_token" so the subsequent creation of other_user_headers =
{"Authorization": f"Bearer {login_response.json()['access_token']}" } is safe;
update any test helper or error message to make failures explicit.

@github-actions github-actions bot removed the enhancement New feature or request label Jan 20, 2026
@github-actions github-actions bot added the enhancement New feature or request label Jan 20, 2026
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Jan 20, 2026
storage_service: Service for filesystem operations.
flow_id: Allows PUT upsert to create flows with a specific ID for syncing between instances.
fail_on_endpoint_conflict: PUT should fail predictably on conflicts rather than silently renaming.
validate_folder: PUT receives folder_id from external sources that may not exist locally.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps something like: Validates the flow's folder_id exists within this project when performing a flow upsert

if flow.user_id is None:
flow.user_id = user_id
# Validate folder_id if requested
if validate_folder and flow.folder_id is not None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd err on the side of failing if the flow.folder_id is None when validate_folder is enabled.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the context of the function, I agree. On the upsert case, I think if the user doesn't provide a folder id, we could just add it to the default folder. WDYT?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From calling upsert function, I'd set validate_folder=False, then use the default folder. I think that means we're in agreement

storage_service=storage_service,
flow_id=flow_id,
fail_on_endpoint_conflict=True,
validate_folder=True,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(If you update new_flow to fail on empty folder_id, might need to set this to false)

)
).first()
if endpoint_conflict:
raise HTTPException(status_code=409, detail="Endpoint name must be unique")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should endpoint_name uniqueness be enforced at the database layer rather than here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It already is. This is just so we can more easily catch the problem.

@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Jan 20, 2026
…attern

_new_flow now handles flush, refresh, and filesystem save internally,
returning FlowRead directly. This eliminates duplication between POST
and PUT endpoints. PUT endpoint now uses single return with variable
status code.
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Jan 20, 2026
_new_flow now handles persistence and returns FlowRead directly,
so the upload endpoint no longer needs to call flush/refresh/save.
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Jan 20, 2026
# Conflicts:
#	src/lfx/src/lfx/_assets/component_index.json
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Jan 20, 2026
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Jan 20, 2026
if flow.user_id is None:
flow.user_id = user_id
# Validate folder_id if requested
if validate_folder and flow.folder_id is not None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From calling upsert function, I'd set validate_folder=False, then use the default folder. I think that means we're in agreement

@ogabrielluiz ogabrielluiz added this pull request to the merge queue Jan 20, 2026
@github-actions github-actions bot added the lgtm This PR has been approved by a maintainer label Jan 20, 2026
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Jan 20, 2026
@ogabrielluiz ogabrielluiz added this pull request to the merge queue Jan 20, 2026
Merged via the queue into main with commit 6f30ed2 Jan 20, 2026
50 of 51 checks passed
@ogabrielluiz ogabrielluiz deleted the feat/add-patch-flows branch January 20, 2026 20:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request lgtm This PR has been approved by a maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants