diff --git a/docs/docs/guides/submitting-a-project.md b/docs/docs/guides/submitting-a-project.md
new file mode 100644
index 0000000..00b8d37
--- /dev/null
+++ b/docs/docs/guides/submitting-a-project.md
@@ -0,0 +1,97 @@
+# Submit a project
+
+Authenticated users can publish Matrix bots, integrations, SDKs, and other
+ecosystem projects directly from the directory.
+
+## Before you start
+
+Prepare the following information:
+
+- A project name
+- A short summary for directory cards and search results
+- A longer description of what the project does and how to use it
+- At least one repository or project website
+- At least one category
+
+You may also provide a Matrix room and indicate whether the project supports
+end-to-end encrypted rooms.
+
+!!! note "Listings are public"
+ Do not include secrets, access tokens, private room links, or other
+ information that should not appear in the public directory.
+
+## Publish a listing
+
+1. Sign in to Matrix Directory.
+2. Open your dashboard.
+3. Select **Add listing**.
+4. Complete the required fields. The listing status panel shows which required
+ information is still missing.
+5. Review the directory-card preview.
+6. Select **Publish listing**.
+
+The listing is associated with your authenticated account and published
+immediately. After a successful submission, the application redirects you to
+the new public listing.
+
+## Field requirements
+
+| Field | Requirement |
+| --- | --- |
+| Name | Required; 2–100 characters |
+| Short description | Required; 1–160 characters |
+| About | Required; 1–10,000 characters; Markdown supported |
+| Repository | Optional individually; required if no website is supplied |
+| Website | Optional individually; required if no repository is supplied |
+| Matrix room | Optional |
+| Categories | At least one required; duplicate categories are not accepted |
+| E2EE support | Select only when the project can operate in encrypted Matrix rooms |
+
+Repository, website, and Matrix room values must be absolute `http://` or
+`https://` URLs no longer than 255 characters. Blank optional URLs are treated
+as omitted.
+
+At least one repository or website must remain on the listing when it is
+updated later.
+
+## Markdown descriptions
+
+The **About** editor supports CommonMark formatting, including:
+
+- Headings
+- Emphasis and strong emphasis
+- Ordered and unordered lists
+- Links
+- Block quotes
+- Inline and fenced code
+- Horizontal rules
+
+Raw HTML is displayed as text rather than interpreted as page markup.
+
+Images embedded with Markdown are not rendered on saved listings. When the
+Markdown is parsed, image nodes are reduced to their alternative text to
+prevent a listing from making visitors contact an untrusted image host. Use a
+normal link when readers need access to a screenshot or diagram.
+
+## Manage your listings
+
+Open the dashboard to see projects owned by your account. Ownership comes from
+your authenticated session; it cannot be assigned to another user through the
+submission form.
+
+Only an owner can update or delete their listing. Deleting a listing is
+permanent, so confirm that you selected the intended project before proceeding.
+
+## Troubleshooting
+
+If a listing cannot be published:
+
+- Check the listing status panel for missing required fields.
+- Confirm that every URL is absolute and begins with `http://` or `https://`.
+- Confirm that at least one repository or website is present.
+- Confirm that at least one category is selected.
+- Shorten any field that exceeds its displayed character limit.
+- Reload the page if categories could not be loaded.
+
+For API-level validation and response details, see the
+[HTTP API reference](../reference/api.md#create-a-project).
diff --git a/docs/docs/index.md b/docs/docs/index.md
index d835cd5..b46843b 100644
--- a/docs/docs/index.md
+++ b/docs/docs/index.md
@@ -124,6 +124,14 @@ profiles, project ownership, and directory data.
+- :material-upload: **Submit a project**
+
+ ---
+
+ Publish a Matrix project and understand its fields, Markdown, and ownership.
+
+ [:octicons-arrow-right-24: Read the submission guide](guides/submitting-a-project.md)
+
- :fontawesome-solid-code: **Development**
---
diff --git a/docs/docs/reference/api.md b/docs/docs/reference/api.md
index 6d3ac60..313b8f5 100644
--- a/docs/docs/reference/api.md
+++ b/docs/docs/reference/api.md
@@ -57,7 +57,7 @@ Application errors use a JSON `detail` field:
| Status | Typical cause |
| --- | --- |
-| `400 Bad Request` | A supplied category ID does not exist |
+| `400 Bad Request` | A category does not exist or an update would remove every project link |
| `401 Unauthorized` | The session cookie is missing, invalid, or expired |
| `404 Not Found` | The resource is missing or unavailable to its current user |
| `422 Unprocessable Content` | The request body or path parameters are invalid |
@@ -99,8 +99,9 @@ GET /api/health
### Create a project
-A project requires at least one valid category. The authenticated user becomes
-the owner; there is no writable `user_id` field.
+A project requires at least one valid category and at least one repository or
+website. The authenticated user becomes the owner; there is no writable
+`user_id` field.
```json
{
@@ -120,13 +121,13 @@ the owner; there is no writable `user_id` field.
| Field | Required | Constraints |
| --- | --- | --- |
| `name` | Yes | 2–100 characters |
-| `description` | Yes | String |
-| `short_description` | Yes | At most 240 characters |
-| `repository_url` | No | String or `null` |
-| `website_url` | No | String or `null` |
-| `matrix_server_url` | No | String or `null` |
+| `description` | Yes | 1–10,000 characters; surrounding whitespace is removed |
+| `short_description` | Yes | 1–160 characters; surrounding whitespace is removed |
+| `repository_url` | Conditional | Absolute HTTP(S) URL up to 255 characters; required when `website_url` is absent |
+| `website_url` | Conditional | Absolute HTTP(S) URL up to 255 characters; required when `repository_url` is absent |
+| `matrix_server_url` | No | Absolute HTTP(S) URL up to 255 characters or `null` |
| `supports_e2ee` | No | Boolean; defaults to `false` |
-| `category_ids` | Yes | Non-empty list of existing category UUIDs |
+| `category_ids` | Yes | Non-empty list of unique, existing category UUIDs |
### Update a project
@@ -141,7 +142,8 @@ the owner; there is no writable `user_id` field.
The required project fields—`name`, `description`, `short_description`,
`supports_e2ee`, and `category_ids`—may be omitted from a patch but cannot
-be explicitly set to `null`. URL fields may be cleared with `null`.
+be explicitly set to `null`. URL fields may be cleared with `null`, but an
+update cannot leave both `repository_url` and `website_url` empty.
### Project response
@@ -169,10 +171,29 @@ Project responses include public owner details and expanded categories:
"id": "11111111-1111-1111-1111-111111111111",
"name": "Bots"
}
- ]
+ ],
+ "created_at": "2026-08-18T12:00:00",
+ "updated_at": "2026-08-18T12:00:00"
}
```
+## Categories
+
+Categories are public reference data used by the submission form.
+
+| Method | Path | Access | Success | Description |
+| --- | --- | --- | --- | --- |
+| `GET` | `/api/categories/` | Public | `200 OK` | List categories alphabetically |
+
+```json
+[
+ {
+ "id": "11111111-1111-1111-1111-111111111111",
+ "name": "Bots"
+ }
+]
+```
+
## Authentication
Authentication endpoints are browser-oriented because login redirects through
@@ -215,6 +236,7 @@ Changing `matrix_id` clears any existing verification. Clients cannot set
## Related documentation
+- [Project submission guide](../guides/submitting-a-project.md)
- [Development guide](../development.md)
- [Authentication architecture](../architecture/authentication.md)
- [Interactive API documentation](https://matrix-directory.codesociety.xyz/api/docs)
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml
index 8708c66..1584eba 100644
--- a/docs/mkdocs.yml
+++ b/docs/mkdocs.yml
@@ -83,6 +83,7 @@ markdown_extensions:
nav:
- Home: index.md
- Guides:
+ - Submit a project: guides/submitting-a-project.md
- Development: development.md
- Reference:
- HTTP API: reference/api.md