diff --git a/CHANGELOG.md b/CHANGELOG.md
index 96774cf..67799d7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@
- Add optional `status` column for product imports and exports.
- Refactor the Export Product sidebar button.
- Set `promotable` to true by default
+- Rewrote docs
## 2.0.5 - 2026-02-19
diff --git a/README.md b/README.md
index d5d89cb..6e82c14 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,60 @@
-
+
+
# Variant Manager
-A plugin for managing products and their variants in Craft Commerce.
+A Craft CMS plugin that imports and exports Craft Commerce product **variants** from CSV files.
+
+## What it does
+
+- Imports a CSV to create or update a Craft Commerce product and its variants.
+- Bulk-imports many products at once from a zip of CSVs, each file becoming its own product.
+- Exports a product to CSV from the product edit page, or many products at once from the Variants element index.
+- Adds a **Variant Attributes** field that stores option name and value pairs (Color, Size, Material) on each variant for filtering on the storefront.
+- Logs each import and export, with configurable retention, in a dashboard activity feed.
+
+## Requirements
+
+- Craft CMS `^5.0`
+- Craft Commerce `^5.0`
+- PHP `^8.2`
+
+## Install
+
+```sh
+composer require fostercommerce/variant-manager
+./craft plugin/install variant-manager
+```
+
+See [`docs/installation.md`](./docs/installation.md) for the full installation and configuration guide.
+
+## Importing
+
+Upload a CSV (or a zip of CSVs) from **Variant Manager -> Dashboard**. The CSV's filename determines the product: a new filename creates a new product, an existing product title updates that product. Each row becomes one variant. Columns map to product fields, variant fields, per-site Commerce fields, inventory levels, and variant attributes.
+
+See [`docs/user-guide/importing.md`](./docs/user-guide/importing.md) and [`docs/user-guide/csv-format.md`](./docs/user-guide/csv-format.md).
+
+## Exporting
+
+Two ways to export: from a single product's edit page (sidebar **Export Product** button), or from the **Variants** element index using the **Export Variant Data** action on a multi-select. A single product downloads as one CSV; multiple products download as a zip. Exported CSVs are shaped so they can be reimported without edits to the column headers.
+
+See [`docs/user-guide/exporting.md`](./docs/user-guide/exporting.md).
+
+## Variant Attributes field
+
+The plugin ships a **Variant Attributes** field type that you add to each product type's variant field layout. The field stores the option-name and option-value pairs from your CSV (Color: Red, Size: Small) as JSON on the variant, and exposes them to Twig for variant selectors and faceted filtering.
+
+See [`docs/reference/field-type.md`](./docs/reference/field-type.md) for storage and Twig usage.
+
+## Permissions
+
+In addition to `accessPlugin-variant-manager`:
+
+- `variant-manager:import`, upload CSVs and create or update products and variants.
+- `variant-manager:export`, export products from the product edit page or the variants index.
+- `variant-manager:manage`, clear the activity log and manage plugin data.
+
+See [`docs/reference/permissions.md`](./docs/reference/permissions.md).
-## Setup and Usage
+## License
-[See docs](/docs)
+Proprietary.
diff --git a/docs/README.md b/docs/README.md
deleted file mode 100644
index 94ddace..0000000
--- a/docs/README.md
+++ /dev/null
@@ -1,25 +0,0 @@
-# Variant Manager Docs
-
-## Getting started
-
-1. [Setup](getting-started/setup.md)
-2. [Configuration](getting-started/configuration.md)
-3. [Field Type](getting-started/field.md)
-4. [Permissions](getting-started/permissions.md)
-
-## Usage
-
-1. [Overview](usage/overview.md)
-2. [Importing Product Variants](usage/importing.md)
-3. [Exporting Product Variants](usage/exporting.md)
-4. [Queues](usage/queues.md)
-
-## Templating
-
-1. [Template Tags](templates/function-reference.md)
-2. [Querying Variants](element-queries/variant-queries.md)
-
-## Template Recipes
-
-1. [Select a variant and add to cart](recipes/add-to-cart.md)
-1. [Filter variants based on a selection examples](recipes/variant-filter.md)
diff --git a/docs/dev-guide/custom-queue.md b/docs/dev-guide/custom-queue.md
new file mode 100644
index 0000000..2bfcb58
--- /dev/null
+++ b/docs/dev-guide/custom-queue.md
@@ -0,0 +1,74 @@
+# Custom queue
+
+How to keep Variant Manager's import jobs from delaying other Craft queue work. Audience: developers running production sites with high queue volume.
+
+Bulk imports can generate thousands of import jobs. By default they go on Craft's main queue, which means other Craft work (search index rebuilds, image transforms, emails) can sit behind a long import batch.
+
+Two ways to address this: lower the priority of Variant Manager jobs, or send them to a custom queue.
+
+## Lower job priority
+
+In a site module, listen for `Queue::EVENT_BEFORE_PUSH` and bump the priority of `ImportJob` instances. Higher priority numbers run later.
+
+```php
+use fostercommerce\variantmanager\jobs\Import as ImportJob;
+use yii\base\Event;
+use yii\queue\PushEvent;
+use yii\queue\Queue;
+
+Event::on(
+ Queue::class,
+ Queue::EVENT_BEFORE_PUSH,
+ static function (PushEvent $event): void {
+ if ($event->job instanceof ImportJob) {
+ // UpdateSearchIndex jobs have a priority of 2048; pushing above that means imports run after search updates.
+ $event->priority = 2049;
+ }
+ }
+);
+```
+
+Wire this into your module's `init()` method.
+
+## Run imports on a dedicated queue
+
+Configure Variant Manager to push its jobs to a separate Yii queue, so they run on a different worker (or run alongside the main queue without blocking it).
+
+In `config/app.php`:
+
+```php
+return [
+ 'bootstrap' => ['priorityQueue'],
+ 'components' => [
+ 'plugins' => [
+ 'pluginConfigs' => [
+ 'variant-manager' => [
+ 'queue' => 'priorityQueue',
+ ],
+ ],
+ ],
+ 'priorityQueue' => [
+ 'class' => \craft\queue\Queue::class,
+ 'channel' => 'priority',
+ ],
+ ],
+];
+```
+
+The string `priorityQueue` is the component handle Variant Manager will resolve at runtime; it can be anything as long as the component is registered.
+
+Then run the worker for the custom queue separately:
+
+```sh
+./craft queue/run --queue=priorityQueue
+```
+
+See Craft's [custom queues guide](https://craftcms.com/docs/5.x/system/queue.html#custom-queues) for more on how Yii's queue components are wired up.
+
+## Verifying it works
+
+1. Upload a small CSV from **Variant Manager -> Dashboard**. The upload modal returns "File ... has been queued for processing" as usual.
+2. With the main queue worker stopped, check the dashboard activity log; the new row stays in its pending state because the main queue does not own the job.
+3. Start the custom queue worker: `./craft queue/run --queue=priorityQueue`.
+4. Refresh the dashboard. The activity log row flips to the green-dot success state once the worker drains the import.
+5. For the priority approach, push two jobs back to back (an import and a search index rebuild) and confirm the search rebuild runs first.
diff --git a/docs/dev-guide/template-tags.md b/docs/dev-guide/template-tags.md
new file mode 100644
index 0000000..2d716f3
--- /dev/null
+++ b/docs/dev-guide/template-tags.md
@@ -0,0 +1,69 @@
+# Template tags
+
+Twig helpers Variant Manager exposes on the `craft` variable for use in storefront templates.
+
+## `craft.variantManager.getAttributeOptions(product, only?)`
+
+Returns the distinct attribute names and the unique values used across a product's variants. Useful for building variant pickers and faceted filters.
+
+Parameters:
+
+- `product`: a `Product` element or a product ID.
+- `only` (optional): a string or array of attribute names to limit the result to.
+
+Returns an array of associative arrays, each with:
+
+- `name`: the attribute name.
+- `values`: a deduplicated array of every value used by any variant for that attribute.
+
+### Example output
+
+```twig
+{% set attributeOptions = craft.variantManager.getAttributeOptions(product.id) %}
+{{ attributeOptions | json_encode }}
+```
+
+Renders something like:
+
+```json
+[
+ { "name": "Color", "values": ["Red", "Blue"] },
+ { "name": "Size", "values": ["Small", "Medium", "Large"] }
+]
+```
+
+### Example: build a radio picker for every attribute
+
+```twig
+{% set product = craft.products().id(30).one() %}
+
+{% for attribute in craft.variantManager.getAttributeOptions(product) %}
+
+{% endfor %}
+```
+
+### Example: limit to specific attributes
+
+```twig
+{% set colorsAndSizes = craft.variantManager.getAttributeOptions(product, ['Color', 'Size']) %}
+```
+
+Pass a single string for one attribute:
+
+```twig
+{% set colors = craft.variantManager.getAttributeOptions(product, 'Color') %}
+```
+
+## Related
+
+- [Querying variants](./twig-queries.md), filtering `craft.variants()` by attribute values.
+- [Add to cart recipe](../recipes/add-to-cart.md), a full variant picker that adds to cart.
+- [Variant filter recipe](../recipes/variant-filter.md), client-side filtering examples.
diff --git a/docs/dev-guide/twig-queries.md b/docs/dev-guide/twig-queries.md
new file mode 100644
index 0000000..66b53fb
--- /dev/null
+++ b/docs/dev-guide/twig-queries.md
@@ -0,0 +1,103 @@
+# Querying variants
+
+How to filter Commerce variants by their Variant Attributes field. Audience: developers building storefront templates or PHP code that queries variants.
+
+Substitute the handle you gave your Variant Attributes field (`variantAttributes`, `myVariantAttributes`, anything you chose) for `variantAttributes` in the examples below.
+
+## Three filter shapes
+
+The Variant Attributes field accepts:
+
+1. A string. Returns variants that have **any** attribute with that value.
+2. An associative array. Returns variants that match **every** name/value pair.
+3. A list of strings and/or associative arrays. Returns variants that match **any** of the entries.
+
+## Filter by an option value
+
+Find variants whose Variant Attributes contains the value `Red` under any attribute name.
+
+PHP:
+
+```php
+\craft\commerce\elements\Variant::find()
+ ->variantAttributes('Red')
+ ->all();
+```
+
+Twig:
+
+```twig
+{% set variants = craft.variants().variantAttributes('Red').all() %}
+```
+
+## Filter by all attribute and option pairs (AND)
+
+Find variants that have `Color = Red` AND `Size = Small`.
+
+PHP:
+
+```php
+\craft\commerce\elements\Variant::find()
+ ->variantAttributes([
+ 'Color' => 'Red',
+ 'Size' => 'Small',
+ ])
+ ->all();
+```
+
+Twig:
+
+```twig
+{% set filter = {
+ 'Color': 'Red',
+ 'Size': 'Small'
+} %}
+{% set variants = craft.variants().variantAttributes(filter).all() %}
+```
+
+## Filter by any attribute and option pair (OR)
+
+Find variants that match any of the entries in a list. Each entry can be a value-only string or a `Name => Value` pair.
+
+PHP:
+
+```php
+\craft\commerce\elements\Variant::find()
+ ->variantAttributes([
+ ['Color' => 'Red'],
+ 'Cotton',
+ ['Size' => 'Small'],
+ ])
+ ->all();
+```
+
+Twig:
+
+```twig
+{% set filter = [
+ { 'Color': 'Red' },
+ 'Cotton',
+ { 'Size': 'Small' }
+] %}
+{% set variants = craft.variants().variantAttributes(filter).all() %}
+```
+
+## How it works
+
+The field stores attributes as JSON. The query builder generates database conditions tailored to your database:
+
+- MySQL: `json_search()` against the field's JSON path, with one condition per name/value pair.
+- PostgreSQL: `@>` containment against the JSON column.
+
+You do not need to do anything special to enable this; both paths are picked automatically.
+
+## Errors
+
+- `$value items must be associative arrays or strings`: a list entry was neither. Check that every element of the array is a string or an `{ name: value }` map.
+- `$value must be either an array or a string`: a non-string, non-array value was passed (a number, a Date, an Element). Convert to a string before passing.
+
+## Related
+
+- [Template tags](./template-tags.md), the `getAttributeOptions` helper.
+- [Add to cart recipe](../recipes/add-to-cart.md).
+- [Variant filter recipe](../recipes/variant-filter.md).
diff --git a/docs/element-queries/variant-queries.md b/docs/element-queries/variant-queries.md
deleted file mode 100644
index a326494..0000000
--- a/docs/element-queries/variant-queries.md
+++ /dev/null
@@ -1,76 +0,0 @@
-# Querying Variants
-
-If a `VariantAttributesField` is attached to a product types variants, then it is possible to use that field to filter
-variants by the attributes and options stored in that field.
-
-The Variant Attributes field accepts 3 formats of filters:
-
-- Filter by an option value, regardless of attribute;
-- Filter _all_ provided attribute/option pairs;
-- And, filter by any of the provided attribute/option pairs.
-
-## Filter by an option value
-
-The following will return all variants which have the option `'Value'`:
-
-#### PHP
-
-```php
-\craft\commerce\elements\Variant::find()->myVariantAttributes('Value')->all();
-```
-
-#### Twig
-
-```twig
-{% craft.variants().myVariantAttributes("Value").all() %}
-```
-
-## Filter by all attribute/option pairs
-
-The following will return all variants which have an attribute of `Attribute A` with a value of `Value A1`, _and_ an
-attribute of `Attribute B` with a value of `Value B1`.
-
-#### PHP
-
-```php
-\craft\commerce\elements\Variant::find()->myVariantAttributes([
- 'Attribute A' => 'Value A1',
- 'Attribute B' => 'Value B1',
-])->all();
-```
-
-#### Twig
-
-```twig
-{% set filter = {
- 'Attribute A': 'Value A1',
- 'Attribute B': 'Value B1',
-} %}
-{% set variants = craft.variants().myVariantAttributes(filter).all() %}
-```
-
-## Filter by any attribute/option pairs
-
-The following will return all variants which have an attribute of `Attribute A` with a value of `Value A1`, _or_ any
-option value of `Value`, _or_ an attribute of `Attribute B` with a value of `Value B1`.
-
-#### PHP
-
-```php
-\craft\commerce\elements\Variant::find()->myVariantAttributes([
- ['Attribute A' => 'Value A1'],
- 'Value',
- ['Attribute B' => 'Value B1'],
-])->all();
-```
-
-#### Twig
-
-```twig
-{% set filter = {
- {'Attribute A': 'Value A1'},
- 'Value',
- {'Attribute B': 'Value B1'},
-} %}
-{% set variants = craft.variants().myVariantAttributes(filter).all() %}
-```
diff --git a/docs/getting-started.md b/docs/getting-started.md
new file mode 100644
index 0000000..80978bd
--- /dev/null
+++ b/docs/getting-started.md
@@ -0,0 +1,119 @@
+# Getting started
+
+This walks you from `composer require` to your first successful CSV import. By the end you will have a Commerce product whose variants came in from a spreadsheet, and you will know the round-trip flow for editing it.
+
+## 1. Install
+
+```sh
+composer require fostercommerce/variant-manager
+./craft plugin/install variant-manager
+```
+
+In the CP you should see a **Variant Manager** nav item with two subnav entries: **Dashboard** and **Variants**.
+
+## 2. Configure
+
+Create `config/variant-manager.php`:
+
+```php
+ '',
+ 'attributePrefix' => 'Attribute: ',
+ 'inventoryPrefix' => 'Inventory',
+ 'activityLogRetention' => '30 days',
+ 'productFieldMap' => [
+ '*' => [
+ 'title' => 'title',
+ 'slug' => 'slug',
+ 'status' => 'status',
+ ],
+ ],
+ 'variantFieldMap' => [
+ '*' => [
+ 'title' => 'title',
+ 'sku' => 'sku',
+ 'inventoryTracked' => 'inventoryTracked',
+ 'price' => 'basePrice',
+ 'height' => 'height',
+ 'width' => 'width',
+ 'length' => 'length',
+ 'weight' => 'weight',
+ ],
+ ],
+];
+```
+
+All keys have defaults; you can skip the file entirely and revisit it once you know which fields you want to import.
+
+## 3. Add the Variant Attributes field
+
+The plugin needs a place to store option name and value pairs (Color: Red, Size: Small) on each variant.
+
+1. **Settings -> Fields -> New field**.
+2. **Field Type**: **Variant Attributes**. Name it **Variant Attributes**, handle `variantAttributes`. No further settings needed.
+3. **Commerce -> Settings -> Product Types -> {your product type} -> Variant Fields**. Drag the new field onto the layout. Save.
+
+You only need one Variant Attributes field per product type's variant layout. Extras are ignored.
+
+## 4. Build your first CSV
+
+Create a file called `Demo Shirt.csv` with this content:
+
+```csv
+title,sku,basePrice[default],inventoryTracked[default],Attribute: Color,Attribute: Size
+Demo Shirt,,,,,
+,DEMO-RED-S,19.99,1,Red,Small
+,DEMO-RED-M,19.99,1,Red,Medium
+,DEMO-BLUE-S,19.99,1,Blue,Small
+,DEMO-BLUE-M,19.99,1,Blue,Medium
+```
+
+The filename matters: `Demo Shirt.csv` tells Variant Manager you want to create a new product titled `Demo Shirt`. Row 2's first cell repeats the title; rows 3-6 are the four variants.
+
+`basePrice[default]` is per-site; replace `default` with your site's handle if it is different. See [CSV format](./user-guide/csv-format.md) for the rest of the columns.
+
+## 5. Upload
+
+**Variant Manager -> Dashboard -> Upload Product**. Pick `Demo Shirt.csv`.
+
+The modal opens with "Are you sure you want to create a new product?" and a **Product Type** dropdown. Pick the product type you added the Variant Attributes field to. Click **Create Product**.
+
+You will see "File Demo Shirt.csv has been queued for processing" and the page refreshes. Once the queue runs the import job, the dashboard's activity log shows a green-dot row: "Imported new product Demo Shirt into {your product type}."
+
+If your queue is not running automatically, run `./craft queue/run`.
+
+## 6. Verify in Commerce
+
+Click the product link in the activity log row. The product opens at **Commerce -> Products -> Demo Shirt**.
+
+Check the variants tab:
+
+- Four variants: DEMO-RED-S, DEMO-RED-M, DEMO-BLUE-S, DEMO-BLUE-M.
+- Each has its price set to 19.99 and inventory tracking on.
+- Open one variant. Scroll to the Variant Attributes field; you should see Color and Size with the value for that variant.
+
+## 7. Round-trip: export, edit, reimport
+
+This is the workflow you will use to bulk-edit existing products.
+
+1. On the product edit page, click **Export Product** in the sidebar. A file called `{id}__demo-shirt.csv` downloads.
+2. Open it in your spreadsheet app. You will see the row layout the import produced, with every Commerce column the plugin can write to.
+3. Change something. Bump the price on DEMO-RED-S to 21.99.
+4. Save the file. **Do not rename it.**
+5. Back to **Variant Manager -> Dashboard -> Upload Product** and pick the same file.
+6. The modal recognises the existing product and asks "Are you sure you want to edit an existing product named \"Demo Shirt\"?" with a **Refresh variants** radio group. Leave it on **Update and remove extra variants** (the default) and click **Edit Product**.
+7. Activity log shows another green-dot row. Reopen the product; DEMO-RED-S is now 21.99.
+
+## 8. Where to go next
+
+For deeper reading:
+
+- [CSV format](./user-guide/csv-format.md), every column the import recognises, with formatting rules.
+- [Importing](./user-guide/importing.md), the upload flow in detail, including the choice between updating and replacing variants.
+- [Exporting](./user-guide/exporting.md), single-product and bulk export.
+- [Bulk import](./user-guide/bulk-import.md), uploading a zip of CSVs.
+- [Troubleshooting](./user-guide/troubleshooting.md), when imports fail.
+- [Variant Attributes field](./reference/field-type.md), how the attribute data is stored and read.
+- [Template tags](./dev-guide/template-tags.md) and [recipes](./recipes/add-to-cart.md), using the attributes on the storefront.
diff --git a/docs/getting-started/configuration.md b/docs/getting-started/configuration.md
deleted file mode 100644
index a731552..0000000
--- a/docs/getting-started/configuration.md
+++ /dev/null
@@ -1,111 +0,0 @@
-# Configuration
-
-Create a `variant-manager.php` file in your `config` directory. The following shows the defaults applied by Variant Manager:
-
-```php
- '',
- 'attributePrefix' => 'Attribute: ',
- 'inventoryPrefix' => 'Inventory: ',
- 'activityLogRetention' => '30 days',
- 'productFieldMap' => [
- '*' => [
- 'title' => 'title',
- 'slug' => 'slug',
- 'status' => 'status',
- ],
- ],
- 'variantFieldMap' => [
- '*' => [
- 'title' => 'title',
- 'sku' => 'sku',
- 'inventoryTracked' => 'inventoryTracked',
- 'price' => 'basePrice',
- 'height' => 'height',
- 'width' => 'width',
- 'length' => 'length',
- 'weight' => 'weight',
- ],
- ],
-];
-```
-
-## Configuration options
-
-### Variant Attributes
-
-When a variant has a `VariantAttributesField` field, the following config options will be used.
-
-**Note** that variant fields may only have a single VariantAttributes field. Any additional fields will be ignored by the plugin.
-
-#### `emptyAttributeValue`
-
-The value to use when a variant's attribute value is empty.
-
-#### `attributePrefix`
-
-The prefix to use to determine which columns correspond to variant attributes when importing/exporting product variants.
-
-#### `inventoryPrefix`
-
-The prefix to use to determine which columns correspond to variant inventory counts when importing/exporting product variants.
-
-#### `activityLogRetention`
-
-The duration for which activity logs should be retained. Accepts values like '1 week', '30 days', etc. Set to `null` or `false` to retain logs indefinitely.
-
-When enabled, activity logs outside the retention period are automatically removed during GC.
-
-Activity logs can also be removed using the `variant-manager/activities/clear` command. Pass `all` to the command to remove all activity logs.
-
-### `productFieldMap`
-
-The map of column names to product properties.
-
-Supported field types and formatting:
-
-- title
-- slug
-- status
-- entries
-
-#### `status` column
-
-Optional. Exports write `enabled` or `disabled`. Imports accept `disabled` (case-insensitive, whitespace trimmed) as disabled; any other value or empty cell imports as enabled. Remove the `'status' => 'status'` entry from `productFieldMap` to skip.
-
-### `variantFieldMap`
-
-The map of column names to variant properties.
-
-This map does _not_ need to include the handle for the VariantAttributes field.
-
-This config option accepts a `'*'` key which would apply the mapping to any product type which isn't included in the map. For example, if you had a product type with an extra field you can include that field with the following config:
-
-```php
- 'sku',
- 'stock' => 'stock',
- 'price' => 'price',
- 'height' => 'height',
- 'width' => 'width',
- 'length' => 'length',
- 'weight' => 'weight',
-];
-
-return [
- 'emptyAttributeValue' => 'None',
- 'attributePrefix' => 'Option : ',
- 'activityLogRetention' => '30 days',
- 'variantFieldMap' => [
- "*" => $defaultFieldMap,
- 'general' => array_merge(
- $defaultFieldMap,
- ['notes' => 'notes']
- ),
- ],
-];
-```
diff --git a/docs/getting-started/field.md b/docs/getting-started/field.md
deleted file mode 100644
index 513692e..0000000
--- a/docs/getting-started/field.md
+++ /dev/null
@@ -1,31 +0,0 @@
-# The Variant Attributes Field
-
-Variant Manager provides a "Variant Attributes" custom field type you need to add to your Craft Commerce variant field
-layouts. This field is used by Variant Manager to save the variant attribute name and value pairs when imports occur,
-and can be used in your Twig templates to allow users to [select a variant and add to cart](recipes/add-to-cart.md) or
-[filter variants based on a selection](../recipes/variant-filter.md).
-
-Content for this field can only be added via Variant Manager's import utility. Once added, a row's value field can be
-manually edited, however, the name cannot. This is intentional to reduce human error.
-
-
-
-## Add the Variant Attributes field to your product types variants
-
-In order for Variant Manager to import your variant data from your spreadsheets, and to use Twig tags in your templates
-to filter and select variants, you will first need to add the "Variant Attributes" custom field included in Variant
-Manager to your product type's variant field layouts.
-
-### 1. Create a "Variant Attributes" field in Craft
-
-Create a new custom field in Craft and select the "Variant Attributes" field type. The field type has no settings so you
-just need to give it a unique name and field handle (ex. "Variant Attributes" and "variantAttributes" respectively).
-
-
-
-### 2. Assign it to your product types variant fields
-
-Now add the custom field you created to your product types variant field layouts for all of your product types that you
-want to be able to import and export, and use twig tags for in your templates to filter and select variants.
-
-
diff --git a/docs/getting-started/permissions.md b/docs/getting-started/permissions.md
deleted file mode 100644
index d3de5df..0000000
--- a/docs/getting-started/permissions.md
+++ /dev/null
@@ -1,26 +0,0 @@
-# Permissions
-
-Variant Manager includes two permissions; One for importing products and variants and one for exporting.
-
-
-
-These can be set on user groups in the Settings → Users section in Craft, or also on specific users in their Permissions
-tab. The two permission settings are :
-
-## Import products and variants
-
-Allows users to import products through the Variant Manager dashboard.
-
-#### `variant-manager:import`
-
-## Export products and variants
-
-Allows users to export products from Craft Commerce's product list or from individual products.
-
-#### `variant-manager:export`
-
-## Manage
-
-Allows users to manage plugin data, such as clearing activity logs.
-
-#### `variant-manager:manage`
diff --git a/docs/getting-started/setup.md b/docs/getting-started/setup.md
deleted file mode 100644
index a1f1245..0000000
--- a/docs/getting-started/setup.md
+++ /dev/null
@@ -1,39 +0,0 @@
-
-# Setup
-
-## Requirements
-
-- Craft CMS 5.0 or later
-- Craft Commerce 5.0 or later
-- PHP 8.2 or greater
-
-## Installation
-
-You can install this plugin from the Plugin Store or with Composer.
-
-#### From the Plugin Store
-
-Go to the Plugin Store in your project’s Control Panel and search for “Variant Manager”. Then press “Install”.
-
-#### With Composer
-
-Open your terminal and run the following commands:
-
-```bash
-# go to the project directory
-cd /path/to/my-project.test
-
-# tell Composer to load the plugin
-composer require fostercommerce/variant-manager
-
-# tell Craft to install the plugin
-./craft plugin/install variant-manager
-```
-
-#### With DDEV
-
-Run the following command from DDEV:
-
-```bash
-ddev composer require fostercommerce/variant-manager -w && ddev exec php craft plugin/install variant-manager
-```
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..3a5e11b
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,23 @@
+# Variant Manager documentation
+
+Import and export Craft Commerce product variants from CSV files.
+
+## Where to go
+
+**First time here?** Start with [Getting started](./getting-started.md), a walkthrough from install to your first successful CSV import.
+
+**Building or editing CSVs for a Craft Commerce store?** See the [user guide](./user-guide/):
+
+- [CSV format](./user-guide/csv-format.md), every column the import recognises, with a working example.
+- [Importing](./user-guide/importing.md), uploading single CSVs and zip batches.
+- [Exporting](./user-guide/exporting.md), getting CSVs out of Commerce to edit.
+- [Troubleshooting](./user-guide/troubleshooting.md), when an import does not behave the way you expected.
+
+**Building on top of the plugin?** See the [developer guide](./dev-guide/), [recipes](./recipes/), and [reference](./reference/):
+
+- [Template tags and queries](./dev-guide/template-tags.md), reading and filtering Variant Attributes in Twig.
+- [Recipes](./recipes/add-to-cart.md), full storefront examples for variant pickers and faceted filtering.
+- [Custom queue](./dev-guide/custom-queue.md), running imports on a dedicated queue.
+- [Configuration reference](./reference/configuration.md), every config key with defaults.
+
+**Setup details:** [Installation](./installation.md).
diff --git a/docs/installation.md b/docs/installation.md
new file mode 100644
index 0000000..25111ae
--- /dev/null
+++ b/docs/installation.md
@@ -0,0 +1,96 @@
+# Installation
+
+A Craft CMS plugin that manages Craft Commerce product variants from CSV files.
+
+## Requirements
+
+- Craft CMS `^5.0`
+- Craft Commerce `^5.0`
+- PHP `^8.2`
+
+## Install
+
+From the Plugin Store, search for **Variant Manager** in **Settings -> Plugins** and press **Install**.
+
+With Composer:
+
+```sh
+composer require fostercommerce/variant-manager
+./craft plugin/install variant-manager
+```
+
+With DDEV:
+
+```sh
+ddev composer require fostercommerce/variant-manager -w && ddev exec php craft plugin/install variant-manager
+```
+
+After install you will see a **Variant Manager** item in the CP navigation with two subnav entries: **Dashboard** and **Variants**.
+
+## Configure
+
+Settings live in a config file, not the Control Panel. Create `config/variant-manager.php`:
+
+```php
+ '',
+ 'attributePrefix' => 'Attribute: ',
+ 'inventoryPrefix' => 'Inventory',
+ 'activityLogRetention' => '30 days',
+ 'productFieldMap' => [
+ '*' => [
+ 'title' => 'title',
+ 'slug' => 'slug',
+ 'status' => 'status',
+ ],
+ ],
+ 'variantFieldMap' => [
+ '*' => [
+ 'title' => 'title',
+ 'sku' => 'sku',
+ 'inventoryTracked' => 'inventoryTracked',
+ 'price' => 'basePrice',
+ 'height' => 'height',
+ 'width' => 'width',
+ 'length' => 'length',
+ 'weight' => 'weight',
+ ],
+ ],
+];
+```
+
+Every key has a default; the plugin runs without the file. See [configuration reference](./reference/configuration.md) for what each key controls.
+
+## Add the Variant Attributes field
+
+The plugin ships a **Variant Attributes** field type. Add it to every Commerce product type whose variants you want to import or export by attribute.
+
+1. **Settings -> Fields -> New field**.
+2. Set the **Field Type** to **Variant Attributes**. Give it a name and handle, for example `variantAttributes`. The field has no settings of its own.
+3. **Commerce -> Settings -> Product Types -> {product type} -> Variant Fields** and drag the field into the variant field layout.
+
+Only one Variant Attributes field per variant field layout is read by the plugin. Additional copies are ignored.
+
+See [Variant Attributes field reference](./reference/field-type.md) for how the field stores data.
+
+## Permissions
+
+Grant the plugin's permissions on user groups in **Users -> {group} -> Permissions** or on individual users:
+
+- `variant-manager:import`, upload CSVs and create or update products and variants.
+- `variant-manager:export`, export products from the product edit page or the variants index.
+- `variant-manager:manage`, clear the activity log and manage plugin data.
+
+`accessPlugin-variant-manager` is required to see the plugin's CP section at all.
+
+See [permissions reference](./reference/permissions.md).
+
+## Console commands
+
+```sh
+./craft variant-manager/activities/clear
+```
+
+Deletes activity log entries older than `activityLogRetention`. Pass `--all` to wipe every entry regardless of age. Craft's garbage collection runs the same expiry pass automatically. See [console commands](./reference/console-commands.md).
diff --git a/docs/recipes/add-to-cart.md b/docs/recipes/add-to-cart.md
index 29f11d3..1d123cc 100644
--- a/docs/recipes/add-to-cart.md
+++ b/docs/recipes/add-to-cart.md
@@ -1,14 +1,16 @@
-# Variant switcher
+# Recipe: select a variant and add to cart
-Select a variant based on a it's attributes and add it to the cart.
+A full storefront example: a product page with a select element for every attribute (Color, Size, Material), where changing a selection picks the matching variant and the form adds that variant to the cart on submit.
+
+Replace `variantAttributes` with the handle of your Variant Attributes field.
## Twig
```twig
{% set attributeOptions = craft.variantManager.getAttributeOptions(product.id) %}
-{% set selection={} %}
+{% set selection = {} %}
{% for attribute in attributeOptions %}
- {# Find the selected option for the attribute using the kebab case of the attribute name #}
+ {# Find the selected option for the attribute using a kebab-case query param #}
{% set selected = craft.app.request.getParam(attribute.name|kebab|ascii) ?? attribute.values|first %}
{% set selection = selection|merge({(attribute.name): selected}) %}
{% endfor %}
@@ -20,22 +22,20 @@ Select a variant based on a it's attributes and add it to the cart.
{{ hiddenInput('purchasableId', variant.id) }}
- {# Show variant selections #}
{% for attribute in attributeOptions %}
{% endfor %}
- {# Show add to cart button #}
-
+
{% js %}
- document.addEventListener('DOMContentLoaded', function() {
- document.querySelectorAll('.attribute-select').forEach(function(select) {
- // When a selection changes, update the page location with a query param for that selection
+ document.addEventListener('DOMContentLoaded', function () {
+ document.querySelectorAll('.attribute-select').forEach(function (select) {
select.addEventListener('change', attributeSelectionChanged);
});
});
- function attributeSelectionChanged(e) {
- let queryParams = new URLSearchParams(window.location.search)
- queryParams.set(e.target.name, e.target.value);
- window.location.search = queryParams.toString()
+ function attributeSelectionChanged(event) {
+ const queryParams = new URLSearchParams(window.location.search);
+ queryParams.set(event.target.name, event.target.value);
+ window.location.search = queryParams.toString();
}
{% endjs %}
```
+
+## How it works
+
+1. `getAttributeOptions` pulls the distinct attribute names and their possible values across the product's variants.
+2. For each attribute, the selected value comes from a query parameter (kebab-cased name) or falls back to the first value.
+3. `craft.variants().productId(product.id).variantAttributes(selection).one()` finds the single variant that matches every selection.
+4. The form posts `purchasableId` to `commerce/cart/update-cart`, adding the matched variant to the cart.
+5. Changing a `