> For the complete documentation index, see [llms.txt](https://apidocs.akinon.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://apidocs.akinon.com/commerce/pretty-urls.md).

# Pretty URLs

A Pretty URL is designed to transform a default URL with query strings into a more user-friendly and search engine-optimized format, enhancing both readability and SEO performance.

### Pretty URL Model

| Parameter         | Type               | Description                                                                                      |
| ----------------- | ------------------ | ------------------------------------------------------------------------------------------------ |
| `old_path`        | `string`           | The original URL containing query strings.                                                       |
| `new_path`        | `string`           | The new SEO-optimized URL.                                                                       |
| `func_name`       | `string`           | The view function used to serve the URL (e.g., `ProductDetailPageApiView`).                      |
| `func_module`     | `string`           | The file name where `func_name` is defined.                                                      |
| `func_initkwargs` | `JSON`             | Keyword arguments passed to the view function.                                                   |
| `viewname`        | `string`           | The URL name associated with `old_path` (e.g., `product`).                                       |
| `args`            | `list`             | The positional arguments parsed from the URL and passed to the view.                             |
| `kwargs`          | `JSON`             | The keyword arguments parsed from the URL and passed to the view.                                |
| `query_params`    | `JSON`             | The query parameters extracted from `old_path`.                                                  |
| `parent`          | `Pretty URL Model` | The generated Pretty URL for the default language in multi-language setups.                      |
| `language`        | `string`           | The language used to slugify values of `UrlGeneratorConfig` placeholders to generate `new_path`. |

### Automatic Pretty URL Generation

When an object of any of the following data models is created, a Pretty URL object is automatically generated for the default language. If multi-language support is enabled, a Pretty URL is generated for each language, with the default language's Pretty URL serving as the parent. These Pretty URLs are then attached to the created object.

Supported data models:

* `Product`
* `CategoryNode`
* `SpecialPage`
* `LandingPage`
* `CategoryNodeLandingPage`
* `Form`

### URL Generation with UrlGeneratorConfig

If a `UrlGeneratorConfig` object exists, the `new_path` is generated using the `template` and `config` fields of that object. The placeholders in the template are slugified and interpolated with corresponding values.

**Example `UrlGeneratorConfig` object:**

```json
{
    "template": "{test}-{test2}",
    "config": {
        "test": "name",
        "test2": "sku"
    },
    "content_type": {
        "pk": 1,
        "model": "product"
    }
}
```

* The `content_type` field specifies the model to which the configuration applies, indicating the type of content or entity that the configuration settings will affect. When a request is made to Omnitron using the endpoint `/api/v1/remote/{channel_id}/content_types/`, Omnitron processes the request and returns a response that includes the `content_type` field.
* The keys in the `config` field correspond to placeholders in the `template`, and the values map to the fields of the created object.

**Example:** For a product created with the following attributes:

```json
{
    "name": "Kırmızı Erkek Gömlek",
    "sku": "5649202702"
}
```

The `new_path` for the generated Pretty URL would be `kirmizi-erkek-gomlek-5649202702`.

### Default Templates for Data Models

If no `UrlGeneratorConfig` is provided, default templates are used for each data model:

| Data model                | Template         |
| ------------------------- | ---------------- |
| `Product`                 | `{slug}-{color}` |
| `CategoryNode`            | `{slug}`         |
| `SpecialPage`             | `{url}`          |
| `LandingPage`             | `{url}`          |
| `CategoryNodeLandingPage` | `{url}`          |
| `Form`                    | `{url}`          |

### How the `new_path` Is Built

For each supported model, the `new_path` is produced by slugifying one or more **source fields** of the object and joining them according to the template. The template defines which fields are used and in what order.

**Default source fields and order:**

| Data model                                                         | Template         | Source fields (in order)                                |
| ------------------------------------------------------------------ | ---------------- | ------------------------------------------------------- |
| `Product`                                                          | `{slug}-{color}` | product **name**, then the **color** (`renk`) attribute |
| `CategoryNode`                                                     | `{slug}`         | category node **name**                                  |
| `SpecialPage` / `LandingPage` / `CategoryNodeLandingPage` / `Form` | `{url}`          | the page's `url` field                                  |

For a product, the default URL is built **only** from the product name and its color attribute. The product `sku`/base code, the category path, and other attributes are **not** part of the default product URL. If a `UrlGeneratorConfig` exists for the content type, its `template` and `config` are used instead, and they determine which fields are used (see "URL Generation with UrlGeneratorConfig" above).

The `new_path` is built as follows:

1. **Slugify each placeholder value.** Values are lowercased, accented and Unicode characters are normalized, and spaces and punctuation are replaced with hyphens. ASCII slugification is used for the default language (and for any language listed in the config's `ascii_languages`); other languages may preserve Unicode characters depending on the `PRETTY_URL_ONLY_ASCII` setting. Setting `PRETTY_URL_FORCE_UNICODE_SLUG` forces Unicode slugification for every language, including the default one.
2. **Drop empty placeholders.** A placeholder with no value is removed along with its separator — e.g. a product with no color yields just the name slug, without a trailing `-`.
3. **Ensure uniqueness.** Because `new_path` is globally unique, if the resulting path is already used by another object a numeric suffix is appended: `-1`, `-2`, and so on.
4. **Generate per-language variants.** If multi-language support is enabled (`PRETTY_URL_MULTI_LANGUAGE`), one Pretty URL is generated per language; the default-language row is the `parent` and the others reference it through the `parent` field. Whether the default language is included as a path prefix is controlled by the [`PrettyUrlIncludeDefaultLanguage`](https://github.com/akinon/docs/tree/main/technicalguides/commerce/dynamic-settings.md) (`PRETTY_URL_INCLUDE_DEFAULT_LANGUAGE`) dynamic setting.

**Example:** a product named `Kırmızı Erkek Gömlek` with a color of `Kırmızı`, using the default template `{slug}-{color}`, produces the `new_path` `kirmizi-erkek-gomlek-kirmizi` — or `kirmizi-erkek-gomlek` if the product has no color attribute.

### When a Pretty URL Is Regenerated

A Pretty URL is generated once when the object is created, and may be **regenerated** afterward. What happens depends on the trigger:

* **On create:** the Pretty URL is generated once. If a Pretty URL already exists for the object, the existing value is preserved and not recomputed.
* **On product update:** when a product is updated, its Pretty URL is regenerated from the object's **current** field values. So if the product **name or color changes**, the product's URL changes accordingly.
* **On category node update:** the category node's URL is likewise regenerated from its current name.
* **On configuration or bulk change:** applying or changing a `UrlGeneratorConfig`, or changing a related dynamic setting such as `PrettyUrlIncludeDefaultLanguage`, triggers a background job that regenerates the Pretty URLs of the affected objects. This bulk regeneration only covers content types that **have** a `UrlGeneratorConfig`; an object that uses the default template (for example a product with no config) is regenerated only when the object itself is next updated, not by the setting change alone.

In every regeneration case, the `new_path` is recomputed from the object's current field values and the active template — it is **not** carried over from whatever the URL happened to be before.

### Why Manual URL Edits Are Not Persisted

The Pretty URL API exposes `POST`, `PATCH`, `PUT`, and `DELETE`, and a manual `PATCH` to `new_path` does take effect immediately. However, **a manually edited URL is not durable and should not be relied upon.**

A Pretty URL is a *derived* value: it is always computed from its source object (product name and color, category name, or the object's `UrlGeneratorConfig` template). There is **no flag** that marks a Pretty URL as "manually set" or "locked", so the system cannot tell a hand-edited URL apart from a generated one.

The next time the object is regenerated — for example when the product is edited, a `UrlGeneratorConfig` is applied, or a related dynamic setting is changed — the URL is recomputed from the source fields and the manually set value is overwritten (a redirect is created from the manual value to the regenerated one). This is why a `new_path` that was set manually through `PATCH` can appear to "revert on its own" some time later, even though no one changed it again.

{% hint style="warning" %}
Do not use `PATCH /api/v1/pretty_urls/{pk}/` to permanently set a product or category URL. The value will be overwritten the next time the object is regenerated. To change a URL durably, change the source data or the URL template instead (see below).
{% endhint %}

### Changing a URL the Right Way, and Old-URL Redirects

Because Pretty URLs are derived, change the **source**, not the Pretty URL row:

* To change a **product** URL, change the product **name** (and/or its color attribute), or change the product's `UrlGeneratorConfig` `template`/`config` so the URL is built from different fields. The URL is regenerated on the next update.
* To change a **category** URL, change the category node **name**.
* To roll a template change out to objects that already exist, (re)apply the relevant `UrlGeneratorConfig`; this regenerates all affected Pretty URLs in the background.

**Old-URL redirects are handled automatically.** Whenever a `new_path` changes, the system creates a redirect from the previous URL to the new one, so existing links and indexed search-engine results continue to resolve. Deleting a Pretty URL likewise leaves a redirect from its `new_path` back to the original `old_path`. You do not need to create these redirects manually.

A Pretty URL that is attached to a product or category also cannot be deleted directly — the `DELETE` request returns `406 Not Acceptable` (see the `DELETE` section below) — precisely because the URL is owned and regenerated by that object.

:::infoNOTE\
All endpoints require authentication. Ensure the header is set as specified before sending a request.

Refer to the Login documentation to obtain the authorization key.\
:::

### `GET` List Pretty URLs

This method is used to retrieve a list of Pretty URL objects.

**Path:** `https://{commerce_url}/api/v1/pretty_urls/`

**Headers:**

```json
{
    "Authorization": "Token {key}",
    "Content-Type": "application/json"
}
```

**Request Parameters**

| Parameter            | Type     | Description                                                         |
| -------------------- | -------- | ------------------------------------------------------------------- |
| `created_date__gt`   | `date`   | Retrieve objects created after this date.                           |
| `created_date__gte`  | `date`   | Retrieve objects created on or after this date.                     |
| `created_date__lt`   | `date`   | Retrieve objects created before this date.                          |
| `created_date__lte`  | `date`   | Retrieve objects created on or before this date.                    |
| `modified_date__gt`  | `date`   | Retrieve objects modified after this date.                          |
| `modified_date__gte` | `date`   | Retrieve objects modified on or after this date.                    |
| `modified_date__lt`  | `date`   | Retrieve objects modified before this date.                         |
| `modified_date__lte` | `date`   | Retrieve objects modified on or before this date.                   |
| `language`           | `string` | Filter by language.                                                 |
| `new_path`           | `string` | Filter by the new URL path.                                         |
| `new_path__exact`    | `string` | Filter by the exact value of the new URL path.                      |
| `old_path`           | `string` | Filter by the old URL path.                                         |
| `path`               | `string` | Looks up in `new_path` first; if not found, searches in `old_path`. |
| `parent`             | `int`    | Filter by the id of the parent Pretty URL.                          |

:::infoNOTE\
If a parameter not listed above is provided, the first page of all objects will be returned.\
:::

**Example Response (200 OK)**

```json
{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
	{
  	"pk": 4180,
  	"new_path": "/testtest/",
  	"old_path": "/special-page/17/",
  	"parent": null,
  	"language": "tr-tr",
  	"func_module": "omnishop.cms.resources.views",
  	"func_name": "SpecialPageView",
  	"func_initkwargs": {},
  	"args": [],
  	"kwargs": {
    	"special_page_id": "17"
  	},
  	"query_params": {},
  	"viewname": "special-page-detail",
  	"created_date": "2019-11-05T08:18:19.488976Z",
  	"modified_date": "2019-11-05T08:18:19.489008Z",
  	"prettyurl_set": []
	}
  ]
}
```

### `GET` Retrieve a Pretty URL

This method is used to retrieve a single Pretty URL by its primary key (pk).

**Path:** `https://{commerce_url}/api/v1/pretty_urls/{pk}`

**Example Response (200 OK)**

```json
{
    "pk": "<pk>",
    "new_path": "<new_path>",
    "old_path": "<old_path>",
    "parent": null,
    "language": "tr-tr",
    "func_module": "omnishop.products.resources.views",
    "func_name": "ProductDetailPageApiView",
    "func_initkwargs": {},
    "args": [],
    "kwargs": {
        "product_id": "1924"
    },
    "query_params": {},
    "viewname": "product",
    "created_date": "2017-01-24T10:32:10.487793Z",
    "modified_date": "2017-01-30T17:05:10.327456Z",
    "prettyurl_set": []
}
```

### `POST` Create a Pretty URL

**Path:** `https://{commerce_url}/api/v1/pretty_urls/`

This method is used to create a new Pretty URL.

**Request Body**

```json
{
	"old_path": "<old_path>",
	"new_path": "<new_path>"
}
```

**Example Responses**

**200 OK:**

```json
{
    "pk": 4186,
    "new_path": "<new_path>",
    "old_path": "<old_path>",
    "parent": null,
    "language": "tr-tr",
    "func_module": "omnishop.products.resources.views",
    "func_name": "ProductDetailPageApiView",
    "func_initkwargs": {},
    "args": [],
    "kwargs": {
        "extra_query_params": {},
        "product_id": "4"
    },
    "query_params": {},
    "viewname": "product",
    "created_date": "2023-09-20T13:39:52.562534Z",
    "modified_date": "2023-09-20T13:39:52.562610Z",
    "prettyurl_set": []
}
```

**406 Not Acceptable:**

```json
{
    "non_field_errors": "Url <old_path> bulunmuyor.",
    "error_code": "pretty_url_100_2"
}
```

```json
{
    "non_field_errors": "<new_path> - <language> pretty url mevcut.",
    "error_code": "pretty_url_100_1"
}
```

### `PATCH` Update a Pretty URL

This method is used to ppdate the `new_path` and/or `old_path` fields of an existing Pretty URL.

:::infoNOTE\
The PUT method functions similarly to PATCH but requires both `new_path` and `old_path` to be provided.\
:::

**Path:** `https://{commerce_url}/api/v1/pretty_urls/{pk}`

**Request Body**

```json
{
	"old_path": "<old_path>",
	"new_path": "<new_path>",
}
```

**Example Responses**

**200 OK:**

```json
{
    "pk": 1432,
    "new_path": "<new_path>",
    "old_path": "<old_path>",
    "parent": null,
    "language": "tr-tr",
    "func_module": "omnishop.products.resources.views",
    "func_name": "ProductDetailPageApiView",
    "func_initkwargs": {},
    "args": [],
    "kwargs": {
        "product_id": "1924"
    },
    "query_params": {},
    "viewname": "product",
    "created_date": "2017-01-24T10:32:10.487793Z",
    "modified_date": "2023-09-20T19:37:52.976216Z",
    "prettyurl_set": []
}
```

**406 Not Acceptable:**

```json
{
    "non_field_errors": "Url <old_path> bulunmuyor.",
    "error_code": "pretty_url_100_2"
}
```

```json
{
    "non_field_errors": "<new_path> - <language> pretty url mevcut.",
    "error_code": "pretty_url_100_2"
}
```

### `DELETE` Delete a Pretty URL

This method is used to delete a Pretty URL by its primary key (pk).

**Path:** `https://{commerce_url}/api/v1/pretty_urls/{pk}`

**Example Responses**

**204 No Content:**

204 No Content successful response status code indicates that a Pretty URL has been deleted.

**406 Not Acceptable:**

```json
{
    "non_field_errors": "Cannot delete some instances of model 'PrettyUrl' because they are referenced through a protected foreign key: 'Product.pretty_url': <QuerySet [<Product: [Bad Unicode data]>]>"
}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

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

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://apidocs.akinon.com/commerce/pretty-urls.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
