# Create a Module Schema

> 📖 **Reference:** For complete technical details, see the [Module Schema Reference](https://docs.stacksync.com/two-way-sync/developers/module-schema-reference)

#### What You'll Learn <a href="#what-youll-learn" id="what-youll-learn"></a>

By following this guide, you'll learn to:

* Create basic schemas with different field types
* Add dynamic content that loads from your API
* Implement field validation and conditional logic
* Test and deploy your schemas

#### Step 1: Locate Your Schema File <a href="#step-1-locate-your-schema-file" id="step-1-locate-your-schema-file"></a>

**Step 1.1:** Navigate to your connector project folder.

**Step 1.2:** Open the `src/modules/` directory.

**Step 1.3:** Find your action folder (e.g., `create_contacts`, `get_posts`).

**Step 1.4:** Open the version folder (e.g., `v1/`).

**Step 1.5:** Locate the `schema.json` file.

Your file path should look like: `src/modules/your_action/v1/schema.json`

#### Step 2: Create Your First Schema <a href="#step-2-create-your-first-schema" id="step-2-create-your-first-schema"></a>

**Step 2.1:** Open your `schema.json` file in your code editor.

**Step 2.2:** Start with this basic structure:

```
{
  "metadata": {
    "workflows_module_schema_version": "1.0.0"
  },
  "fields": [],
  "ui_options": {}
}
```

**Step 2.3:** Save the file.

**Step 2.4:** Test that your connector loads without errors.

> 💡 **Tip:** Always start with the minimal structure and build incrementally.

#### Step 3: Add Your First Field <a href="#step-3-add-your-first-field" id="step-3-add-your-first-field"></a>

**Step 3.1:** Add a simple text field inside the `fields` array:

```
{
  "metadata": {
    "workflows_module_schema_version": "1.0.0"
  },
  "fields": [
    {
      "id": "api_key",
      "type": "string",
      "label": "API Key"
    }
  ]
}
```

**Step 3.2:** Save and reload your connector.

**Step 3.3:** Create a test workflow and add your action.

**Step 3.4:** Verify the field appears in the form.

> 📖 **Reference:** See [Field Types](https://docs.stacksync.com/two-way-sync/developers/module-schema-reference#field-types) for all available field types.

#### Step 4: Make the Field Required <a href="#step-4-make-the-field-required" id="step-4-make-the-field-required"></a>

**Step 4.1:** Add validation to make the field required:

```
{
  "id": "api_key",
  "type": "string",
  "label": "API Key",
  "validation": {
    "required": true
  }
}
```

**Step 4.2:** Save the file.

**Step 4.3:** Test the validation by trying to save without entering a value.

**Step 4.4:** Verify that an error message appears.

> 📖 **Reference:** See [Validation Rules](https://docs.stacksync.com/two-way-sync/developers/module-schema-reference#validation-rules) for all validation options.

#### Step 5: Add a Password Widget <a href="#step-5-add-a-password-widget" id="step-5-add-a-password-widget"></a>

**Step 5.1:** Add UI options to hide the API key:

```
{
  "id": "api_key",
  "type": "string",
  "label": "API Key",
  "validation": {
    "required": true
  },
  "ui_options": {
    "ui_widget": "password"
  }
}
```

**Step 5.2:** Save and test that the input is now hidden.

> 📖 **Reference:** See [UI Widgets](https://docs.stacksync.com/two-way-sync/developers/module-schema-reference#ui-widgets) for all available widgets.

#### Step 6: Add a Description <a href="#step-6-add-a-description" id="step-6-add-a-description"></a>

**Step 6.1:** Add a helpful description for users:

```
{
  "id": "api_key",
  "type": "string",
  "label": "API Key",
  "description": "Your API key for authentication",
  "validation": {
    "required": true
  },
  "ui_options": {
    "ui_widget": "password"
  }
}
```

**Step 6.2:** Save and verify the description appears below the field.

#### Step 7: Add a Selection Field <a href="#step-7-add-a-selection-field" id="step-7-add-a-selection-field"></a>

**Step 7.1:** Add a second field for platform selection:

```
{
  "fields": [
    {
      "id": "api_key",
      "type": "string",
      "label": "API Key",
      "description": "Your API key for authentication",
      "validation": {
        "required": true
      },
      "ui_options": {
        "ui_widget": "password"
      }
    },
    {
      "id": "platform",
      "type": "object",
      "label": "Platform",
      "ui_options": {
        "ui_widget": "SelectWidget"
      }
    }
  ]
}
```

**Step 7.2:** Save and verify both fields appear.

#### Step 8: Add Static Choices <a href="#step-8-add-static-choices" id="step-8-add-static-choices"></a>

**Step 8.1:** Add choices to the platform field:

```
{
  "id": "platform",
  "type": "object",
  "label": "Platform",
  "ui_options": {
    "ui_widget": "SelectWidget"
  },
  "choices": {
    "values": [
      {
        "value": { "id": "linkedin", "label": "LinkedIn" },
        "label": "LinkedIn"
      },
      {
        "value": { "id": "twitter", "label": "Twitter" },
        "label": "Twitter"
      }
    ]
  }
}
```

**Step 8.2:** Save and test selecting different platforms.

> 📖 **Reference:** See [Choices Configuration](https://docs.stacksync.com/two-way-sync/developers/module-schema-reference#choices-configuration) for choice formats.

#### Step 9: Set Field Order <a href="#step-9-set-field-order" id="step-9-set-field-order"></a>

**Step 9.1:** Add the `ui_options` section at the root level:

```
{
  "metadata": {
    "workflows_module_schema_version": "1.0.0"
  },
  "fields": [...],
  "ui_options": {
    "ui_order": ["api_key", "platform"]
  }
}
```

**Step 9.2:** Save and verify the fields appear in the specified order.

#### Step 10: Add a Large Text Field <a href="#step-10-add-a-large-text-field" id="step-10-add-a-large-text-field"></a>

**Step 10.1:** Add a message field with textarea widget:

```
{
  "id": "message",
  "type": "string",
  "label": "Message",
  "description": "The message to send",
  "ui_options": {
    "ui_widget": "textarea"
  }
}
```

**Step 10.2:** Update the UI order to include the new field:

```
{
  "ui_options": {
    "ui_order": ["api_key", "platform", "message"]
  }
}
```

**Step 10.3:** Save and test the multi-line text area.

#### Step 11: Add Dynamic Content Setup <a href="#step-11-add-dynamic-content-setup" id="step-11-add-dynamic-content-setup"></a>

**Step 11.1:** Add a user selection field that will load dynamically:

```
{
  "id": "user_id",
  "type": "object",
  "label": "User",
  "description": "Select a user",
  "ui_options": {
    "ui_widget": "SelectWidget"
  },
  "choices": {
    "values": []
  },
  "content": {
    "type": ["managed"],
    "content_objects": [
      {
        "id": "users"
      }
    ]
  }
}
```

**Step 11.2:** Add it to your UI order:

```
{
  "ui_options": {
    "ui_order": ["api_key", "platform", "user_id", "message"]
  }
}
```

**Step 11.3:** Save the schema.

> 📖 **Reference:** See [Dynamic Content](https://docs.stacksync.com/two-way-sync/developers/module-schema-reference#dynamic-content) for content configuration.

#### Step 12: Implement Dynamic Content in Code <a href="#step-12-implement-dynamic-content-in-code" id="step-12-implement-dynamic-content-in-code"></a>

**Step 12.1:** Open your `route.py` file in the same folder.

**Step 12.2:** Find the `/content` endpoint (or create it if it doesn't exist).

**Step 12.3:** Add the users content handler:

```
@router.route("/content", methods=["POST"])
def content():
    try:
        request = Request(flask_request)
        data = request.data

        content_object_names = data.get("content_object_names", [])
        content_objects = []

        for content_name in content_object_names:
            if content_name == "users":
                users = [
                    {"value": {"id": "1", "label": "John Doe"}, "label": "John Doe"},
                    {"value": {"id": "2", "label": "Jane Smith"}, "label": "Jane Smith"}
                ]

                content_objects.append({
                    "content_object_name": "users",
                    "data": users
                })

        return Response(data={"content_objects": content_objects})

    except Exception as e:
        return Response.error(str(e))
```

**Step 12.4:** Save the route file.

#### Step 13: Test Dynamic Content <a href="#step-13-test-dynamic-content" id="step-13-test-dynamic-content"></a>

**Step 13.1:** Reload your connector.

**Step 13.2:** Open your test workflow action.

**Step 13.3:** Click the refresh button next to the User field.

**Step 13.4:** Verify that the dropdown loads with John Doe and Jane Smith.

**Step 13.5:** Select a user and save.

#### Step 14: Add Field Dependencies <a href="#step-14-add-field-dependencies" id="step-14-add-field-dependencies"></a>

**Step 14.1:** Add a channel field that will affect the user field:

```
{
  "id": "channel",
  "type": "object",
  "label": "Channel",
  "ui_options": {
    "ui_widget": "SelectWidget"
  },
  "choices": {
    "values": [
      {
        "value": { "id": "general", "label": "General" },
        "label": "General"
      },
      {
        "value": { "id": "tech", "label": "Tech Team" },
        "label": "Tech Team"
      }
    ]
  }
}
```

**Step 14.2:** Update the user field to depend on the channel:

```
{
  "id": "user_id",
  "type": "object",
  "label": "User",
  "description": "Select a user in the channel",
  "ui_options": {
    "ui_widget": "SelectWidget"
  },
  "choices": {
    "values": []
  },
  "content": {
    "type": ["managed"],
    "content_objects": [
      {
        "id": "users_in_channel",
        "content_object_depends_on_fields": [
          {
            "id": "channel"
          }
        ]
      }
    ]
  }
}
```

**Step 14.3:** Update UI order:

```
{
  "ui_options": {
    "ui_order": ["api_key", "platform", "channel", "user_id", "message"]
  }
}
```

> 📖 **Reference:** See [Content Dependencies](https://docs.stacksync.com/two-way-sync/developers/module-schema-reference#content-dependencies) for dependency patterns.

#### Step 15: Update Content Handler for Dependencies <a href="#step-15-update-content-handler-for-dependencies" id="step-15-update-content-handler-for-dependencies"></a>

**Step 15.1:** Update your content endpoint to handle the dependency:

```
for content_name in content_object_names:
    if content_name == "users_in_channel":
        form_data = data.get("form_data", {})
        selected_channel = form_data.get("channel", {})
        channel_id = selected_channel.get("id") if selected_channel else None

        if channel_id == "general":
            users = [
                {"value": {"id": "1", "label": "John Doe"}, "label": "John Doe"},
                {"value": {"id": "3", "label": "Alice Brown"}, "label": "Alice Brown"}
            ]
        elif channel_id == "tech":
            users = [
                {"value": {"id": "2", "label": "Jane Smith"}, "label": "Jane Smith"},
                {"value": {"id": "4", "label": "Bob Wilson"}, "label": "Bob Wilson"}
            ]
        else:
            users = []

        content_objects.append({
            "content_object_name": "users_in_channel",
            "data": users
        })
```

**Step 15.2:** Save and test that users change based on channel selection.

#### Step 16: Add Schema Endpoint for Dynamic Updates <a href="#step-16-add-schema-endpoint-for-dynamic-updates" id="step-16-add-schema-endpoint-for-dynamic-updates"></a>

**Step 16.1:** Create a `/schema` endpoint in your `route.py` file:

```
@router.route("/schema", methods=["POST"])
def schema():
    try:
        request = Request(flask_request)
        data = request.data

        # Get the current form data
        form_data = data.get("form_data", {})

        # Load your base schema from the schema.json file
        with open("schema.json", "r") as f:
            base_schema = json.load(f)

        # Apply any dynamic modifications based on form_data
        # For example, modify field visibility or validation

        return Response(data=base_schema)

    except Exception as e:
        return Response.error(str(e))
```

**Step 16.2:** This endpoint is called when fields with `on_action: {"load_schema": true}` are changed.

**Step 16.3:** The returned schema is merged with the existing schema to update the UI.

#### Step 17: Test Your Complete Schema <a href="#step-17-test-your-complete-schema" id="step-17-test-your-complete-schema"></a>

**Step 17.1:** Save all files.

**Step 17.2:** Reload your connector in the Stacksync interface.

**Step 17.3:** Create a new test workflow.

**Step 17.4:** Add your action to the workflow.

**Step 17.5:** Test each functionality:

* Verify all fields appear in correct order
* Test validation by leaving required fields empty
* Test dynamic content loading with refresh button
* Test field dependencies by changing channel selection

#### Step 18: Handle Data in Execute Endpoint <a href="#step-18-handle-data-in-execute-endpoint" id="step-18-handle-data-in-execute-endpoint"></a>

**Step 18.1:** Open your `route.py` file.

**Step 18.2:** Find the `/execute` endpoint.

**Step 18.3:** Add code to handle your schema data:

```
@router.route("/execute", methods=["POST"])
def execute():
    try:
        request = Request(flask_request)
        data = request.data

        # Get values from your schema
        api_key = data.get("api_key")
        platform = data.get("platform", {})
        channel = data.get("channel", {})
        user_id = data.get("user_id", {})
        message = data.get("message")

        # Use the data for your logic
        result = {
            "success": True,
            "platform": platform.get("label"),
            "user": user_id.get("label"),
            "message": message
        }

        return Response(
            data=result,
            metadata={"processed_at": "2024-01-01T00:00:00Z"}
        )

    except Exception as e:
        return Response.error(str(e))
```

**Step 18.4:** Save and test by running your workflow.

#### Step 19: Add Input Validation <a href="#step-19-add-input-validation" id="step-19-add-input-validation"></a>

**Step 19.1:** Add more validation rules to your message field:

```
{
  "id": "message",
  "type": "string",
  "label": "Message",
  "description": "The message to send",
  "ui_options": {
    "ui_widget": "textarea"
  },
  "validation": {
    "required": true,
    "min_length": 10,
    "max_length": 500
  }
}
```

**Step 19.2:** Test that validation works by entering messages that are too short or too long.

#### Step 20: Final Testing Checklist <a href="#step-20-final-testing-checklist" id="step-20-final-testing-checklist"></a>

**Step 20.1:** Test all validation rules:

* Required fields show errors when empty
* Length limits are enforced
* Format validation works

**Step 20.2:** Test dynamic content:

* Content loads when refreshing
* Dependencies update correctly
* No errors in browser console

**Step 20.3:** Test schema updates:

* Fields with `load_schema: true` trigger schema reloads
* `/schema` endpoint returns updated schemas correctly
* UI updates reflect schema changes

**Step 20.4:** Test workflow execution:

* Data reaches execute endpoint correctly
* All field values are accessible
* Workflow completes successfully

#### Congratulations! <a href="#congratulations" id="congratulations"></a>

You've successfully built a complete module schema with:

* ✅ Basic field types (string, object, number)
* ✅ Field validation and UI widgets
* ✅ Dynamic content loading via `/content` endpoint
* ✅ Field dependencies
* ✅ Schema updates via `/schema` endpoint
* ✅ Integration with your `/execute` endpoint


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.stacksync.com/agentic-workflows/developers/create-a-module-schema.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
