Skip to content

Tag Management Guide

Overview

Tags provide a flexible, flat labelling system for products in the Operations module. Unlike hierarchical categories that enforce a single tree structure, tags let you apply multiple cross-cutting labels to any product - enabling rich filtering, search facets, and promotional groupings without altering the category tree.

Each tag carries a name, a URL-safe slug, an optional description, a hex colour code for UI display, and a simple active/inactive flag. Tags support merge operations for consolidating duplicates and bulk endpoints for efficient catalog maintenance.

Tag Fields

FieldTypeRequiredDescription
idULID stringAuto-generatedUnique identifier, e.g. 01hwxyz123abc456def789ghi0
namestringYesDisplay name (max 100 characters, unique per tenant)
slugstringYesURL-safe identifier (lowercase letters, numbers, hyphens only; max 100 characters; unique per tenant)
descriptionstringNoOptional descriptive text (max 500 characters)
colorstringNoHex colour code, e.g. #3B82F6 (defaults to #3B82F6)
is_activebooleanNoActive status flag (defaults to true)
created_atISO 8601 datetimeAuto-generatedRecord creation timestamp
updated_atISO 8601 datetimeAuto-generatedRecord last-modified timestamp

Tag Lifecycle

Tags follow a simple active/inactive lifecycle identical to brands:

StatusDescriptionEffect on Products
ActiveTag is available for useProducts can be tagged with this label
InactiveTag is suspended from useExisting product associations are preserved; new associations are blocked

Deactivation vs. Deletion

Deactivating a tag is non-destructive. Products already tagged retain the association. Use deactivation when a tag is no longer relevant but historical data should be preserved. Use deletion only when the tag was created in error and has no product associations.

API Endpoints

All tag endpoints are scoped under /api/v1/operations/tags. Every request requires a valid Bearer token.

List Tags

Endpoint: GET /api/v1/operations/tags

Query Parameters:

ParameterTypeDescription
searchstringFilter by name or slug (partial match)
is_activebooleanFilter by active status (true or false)
has_productsbooleanFilter to tags with (true) or without (false) associated products
colorstringFilter by hex colour code
sort_bystringSort field: name, created_at, updated_at (default: name)
sort_directionstringasc or desc (default: asc)
pageintegerPage number (default: 1)
per_pageintegerResults per page (default: 15)

Example Request:

bash
GET /api/v1/operations/tags?is_active=true&sort_by=name&sort_direction=asc&per_page=25

Response (200 OK):

json
{
  "success": true,
  "message": "Tags retrieved successfully",
  "data": [
    {
      "id": "01hwxyz123abc456def789ghi0",
      "name": "Electronics",
      "slug": "electronics",
      "description": "Products related to electronic devices and components",
      "color": "#3B82F6",
      "is_active": true,
      "created_at": "2025-10-01T08:00:00Z",
      "updated_at": "2025-11-15T14:30:00Z"
    },
    {
      "id": "01hwxyz789def012ghi345jkl6",
      "name": "Seasonal",
      "slug": "seasonal",
      "description": "Seasonal and limited-availability products",
      "color": "#F59E0B",
      "is_active": true,
      "created_at": "2025-10-12T11:20:00Z",
      "updated_at": "2025-10-12T11:20:00Z"
    }
  ],
  "meta": {
    "current_page": 1,
    "per_page": 25,
    "total": 24,
    "last_page": 1
  }
}

Create a Tag

Endpoint: POST /api/v1/operations/tags

Request Body:

json
{
  "name": "Electronics",
  "slug": "electronics",
  "description": "Products related to electronic devices and components",
  "color": "#3B82F6",
  "is_active": true
}

Required Fields:

  • name - Display name of the tag (max 100 characters, unique)
  • slug - URL-safe identifier (lowercase letters, numbers, and hyphens only; unique)

Optional Fields:

  • description - Free-text description (max 500 characters)
  • color - Hex colour code (format: #RRGGBB); defaults to #3B82F6
  • is_active - Defaults to true

Response (201 Created):

json
{
  "success": true,
  "message": "Tag created successfully",
  "data": {
    "id": "01hwxyz123abc456def789ghi0",
    "name": "Electronics",
    "slug": "electronics",
    "description": "Products related to electronic devices and components",
    "color": "#3B82F6",
    "is_active": true,
    "created_at": "2025-12-17T10:30:00Z",
    "updated_at": "2025-12-17T10:30:00Z"
  },
  "meta": {}
}

Error Response (422 Unprocessable Entity):

json
{
  "success": false,
  "message": "The given data was invalid.",
  "data": null,
  "meta": {
    "errors": {
      "name": ["A tag with this name already exists"],
      "slug": ["Tag slug must contain only lowercase letters, numbers, and hyphens"]
    }
  }
}

Get Tag Details

Endpoint: GET /api/v1/operations/tags/{id}

Example:

bash
GET /api/v1/operations/tags/01hwxyz123abc456def789ghi0

Response (200 OK):

json
{
  "success": true,
  "message": "Tags retrieved successfully",
  "data": {
    "id": "01hwxyz123abc456def789ghi0",
    "name": "Electronics",
    "slug": "electronics",
    "description": "Products related to electronic devices and components",
    "color": "#3B82F6",
    "is_active": true,
    "created_at": "2025-10-01T08:00:00Z",
    "updated_at": "2025-11-15T14:30:00Z"
  },
  "meta": {}
}

Error Response (404 Not Found):

json
{
  "success": false,
  "message": "Tag not found",
  "data": null,
  "meta": {}
}

Update a Tag

Endpoint: PUT /api/v1/operations/tags/{id}

All fields are optional - send only the fields you want to change.

Request Body:

json
{
  "name": "Home Appliances",
  "slug": "home-appliances",
  "description": "Household and kitchen appliances",
  "color": "#10B981"
}

Response (200 OK):

json
{
  "success": true,
  "message": "Tag updated successfully",
  "data": {
    "id": "01hwxyz123abc456def789ghi0",
    "name": "Home Appliances",
    "slug": "home-appliances",
    "description": "Household and kitchen appliances",
    "color": "#10B981",
    "is_active": true,
    "updated_at": "2025-12-17T14:00:00Z"
  },
  "meta": {}
}

Delete a Tag

Endpoint: DELETE /api/v1/operations/tags/{id}

Before Deleting

Deletion is permanent. If the tag has associated products, consider deactivating instead to preserve historical data. Products will lose the tag association upon deletion.

Response (200 OK):

json
{
  "success": true,
  "message": "Tag deleted successfully",
  "data": null,
  "meta": {}
}

Bulk Operations

Bulk Activate

Endpoint: POST /api/v1/operations/tags/bulk-activate

Request Body:

json
{
  "tag_ids": [
    "01hwxyz123abc456def789ghi0",
    "01hwxyz789def012ghi345jkl6",
    "01hwxyzabc123def456ghi789j0"
  ]
}

Response (200 OK):

json
{
  "success": true,
  "message": "Tags activated successfully",
  "data": {
    "processed": 3,
    "successful": 3,
    "failed": 0
  },
  "meta": {}
}

Bulk Deactivate

Endpoint: POST /api/v1/operations/tags/bulk-deactivate

Request Body:

json
{
  "tag_ids": [
    "01hwxyz123abc456def789ghi0",
    "01hwxyz789def012ghi345jkl6"
  ]
}

Response (200 OK):

json
{
  "success": true,
  "message": "Tags deactivated successfully",
  "data": {
    "processed": 2,
    "successful": 2,
    "failed": 0
  },
  "meta": {}
}

Bulk Delete

Endpoint: POST /api/v1/operations/tags/bulk-delete

Irreversible Operation

Bulk delete is permanent. Review tag-product associations before issuing a bulk delete.

Request Body:

json
{
  "tag_ids": [
    "01hwxyz123abc456def789ghi0",
    "01hwxyz789def012ghi345jkl6"
  ]
}

Response (200 OK):

json
{
  "success": true,
  "message": "Bulk delete completed",
  "data": {
    "processed": 2,
    "successful": 2,
    "failed": 0
  },
  "meta": {}
}

Merge Tags

Endpoint: POST /api/v1/operations/tags/merge

Merge one or more source tags into a target tag. All product associations from source tags are transferred to the target. Source tags are removed after the merge.

Request Body:

json
{
  "source_tag_ids": [
    "01hwxyz123abc456def789ghi0",
    "01hwxyz789def012ghi345jkl6"
  ],
  "target_tag_id": "01hwxyzabc123def456ghi789j0"
}
FieldTypeRequiredDescription
source_tag_idsarray of ULID stringsYesTags to merge from (will be removed)
target_tag_idULID stringYesTag to merge into (will receive all product associations)

Response (200 OK):

json
{
  "success": true,
  "message": "Tags merged successfully",
  "data": {
    "source_tag_ids": [
      "01hwxyz123abc456def789ghi0",
      "01hwxyz789def012ghi345jkl6"
    ],
    "target_tag_id": "01hwxyzabc123def456ghi789j0"
  },
  "meta": {}
}

When to Merge

Merge is useful when duplicate tags accumulate over time (e.g. "Electronics" and "Electronic" both exist). Consolidating into a single tag simplifies filtering and avoids confusion. Always verify which tag has the most product associations and use that as the target.

Tag Statistics

Endpoint: GET /api/v1/operations/tags/statistics

Returns aggregate counts for the tag catalog. Use this endpoint for dashboard widgets or catalog health checks.

Response (200 OK):

json
{
  "success": true,
  "message": "Tags retrieved successfully",
  "data": {
    "total": 24,
    "active": 20,
    "inactive": 4,
    "with_products": 18,
    "without_products": 6
  },
  "meta": {}
}

Export Tags

Endpoint: POST /api/v1/operations/tags/export

Exports tag data in the requested format. The response is the exported file content with an appropriate Content-Disposition header for browser download.

Request Body:

json
{
  "format": "csv"
}
ParameterTypeDescription
formatstringExport format: csv or json

Business Scenarios

Scenario 1: Organising a Product Catalog with Tags

Context: Your product catalog has grown and products need multiple cross-cutting labels beyond their primary category. You want to tag products by material, season, target audience, and promotional status.

Workflow:

  1. Create a set of tags for each dimension (material, season, etc.)
  2. Assign tags to products via the product create/update endpoints
  3. Use the list tags endpoint to verify coverage
  4. Run statistics to check for orphaned tags

API Calls:

bash
# 1. Create material tags
POST /api/v1/operations/tags
{ "name": "Cotton", "slug": "cotton", "color": "#8B5CF6" }

POST /api/v1/operations/tags
{ "name": "Polyester", "slug": "polyester", "color": "#6366F1" }

# 2. Create seasonal tags
POST /api/v1/operations/tags
{ "name": "Summer 2026", "slug": "summer-2026", "color": "#F59E0B" }

# 3. Verify with statistics
GET /api/v1/operations/tags/statistics

Scenario 2: Consolidating Duplicate Tags

Context: Multiple team members have created overlapping tags ("Electronics", "Electronic", "Elec"). You need to merge them into a single canonical tag.

Workflow:

  1. Search for duplicates
  2. Identify the canonical tag (most product associations)
  3. Merge duplicates into the canonical tag
  4. Verify the merge

API Calls:

bash
# 1. Find all electronics-related tags
GET /api/v1/operations/tags?search=electr

# 2. Merge duplicates into the canonical tag
POST /api/v1/operations/tags/merge
{
  "source_tag_ids": [
    "01hwxyz789def012ghi345jkl6",
    "01hwxyzabc123def456ghi789j0"
  ],
  "target_tag_id": "01hwxyz123abc456def789ghi0"
}

# 3. Verify
GET /api/v1/operations/tags/statistics

Scenario 3: Seasonal Tag Cleanup

Context: End-of-season tags from the previous year are cluttering the catalog. Some have products, others are empty.

Workflow:

  1. List inactive or past-season tags
  2. Export the list for review
  3. Deactivate tags still linked to products
  4. Bulk delete empty tags

API Calls:

bash
# 1. Find seasonal tags
GET /api/v1/operations/tags?search=2025&has_products=false

# 2. Export for audit trail
POST /api/v1/operations/tags/export
{ "format": "csv" }

# 3. Bulk delete empty tags
POST /api/v1/operations/tags/bulk-delete
{
  "tag_ids": [
    "01hwxyzaaa111bbb222ccc333d0",
    "01hwxyzeee555fff666ggg777h0"
  ]
}

Best Practices

1. Slug Consistency

Always provide explicit slugs when creating tags. Use lowercase letters, numbers, and hyphens only. A consistent naming convention (e.g. material-cotton, season-summer-2026) makes programmatic filtering and search integration predictable.

2. Colour Coding

Assign distinct colours to tag groups so they are visually distinguishable in the UI. For example, use blue shades for material tags, amber for seasonal tags, and green for promotional tags. The colour is purely cosmetic and has no functional effect.

3. Merge Before Delete

When duplicates are discovered, always merge rather than deleting one tag and manually re-tagging products. The merge endpoint atomically transfers all product associations, ensuring no products lose their labels.

4. Use Statistics for Health Checks

Run GET /api/v1/operations/tags/statistics regularly. A growing without_products count signals tags that were created speculatively and never used - clean them up to keep the catalog tidy.

5. Prefer Tags over Deeply Nested Categories

When a product classification does not naturally fit a hierarchical tree (e.g. "Eco-Friendly", "Best Seller", "Clearance"), use tags instead of creating parallel category trees. Tags are flat, additive, and can be combined freely.

Integration Points

With Products

Tags are associated with products at the product level. When creating or updating a product, reference tags by their ULID. Deactivating a tag does not cascade to products - existing associations are preserved. Refer to the Product Management Guide for details on tag fields in product requests.

With Brands

Tags and brands serve complementary purposes. Brands identify provenance (who made it), while tags classify attributes and usage (what it is, when it is relevant). A product typically has one brand but multiple tags.

With Product Categories

Categories provide the primary tree-based navigation structure. Tags provide secondary, cross-cutting classification. Use categories for "where does this product live in the catalog" and tags for "what characteristics does this product have." See the Category Management Guide for category operations.

Troubleshooting

Tag Creation Fails with Name Conflict

Error: "A tag with this name already exists"

Cause: Another tag in the same tenant already uses the supplied name.

Solution:

  1. Search for the conflicting tag: GET /api/v1/operations/tags?search={name}
  2. If the existing tag is the same concept, consider merging
  3. If the tags are distinct, choose a more specific name

Slug Validation Error

Error: "Tag slug must contain only lowercase letters, numbers, and hyphens"

Cause: The slug contains uppercase letters, spaces, or special characters.

Solution: Use only a-z, 0-9, and - in slugs. Convert spaces to hyphens and strip special characters before submitting.

Merge Fails

Error: Merge operation returns an error.

Cause: One of the source tag IDs or the target tag ID does not exist.

Solution:

  1. Verify all IDs by fetching each tag individually
  2. Ensure no IDs are duplicated between source and target
  3. Retry with corrected IDs

Documentation for SynthesQ CRM/ERP Platform