<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Konsole</title>
    <description>The latest articles on DEV Community by Konsole (@konsole).</description>
    <link>https://dev.to/konsole</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F119100%2F7668f9b9-6c6b-4fb2-bcdb-d21b74070359.png</url>
      <title>DEV Community: Konsole</title>
      <link>https://dev.to/konsole</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/konsole"/>
    <language>en</language>
    <item>
      <title>How We Built an AI-Powered Automated Product Enrichment Pipeline for Shopify</title>
      <dc:creator>Konsole</dc:creator>
      <pubDate>Fri, 25 Apr 2025 09:11:00 +0000</pubDate>
      <link>https://dev.to/konsole/how-we-built-an-ai-powered-automated-product-enrichment-pipeline-for-shopify-4old</link>
      <guid>https://dev.to/konsole/how-we-built-an-ai-powered-automated-product-enrichment-pipeline-for-shopify-4old</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Engineering a fully automated workflow for a Shopify store&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Maintaining a successful e-commerce store comes with its fair share of challenges. It demands constant attention to ever-changing details across inventory, customer experience, and platform updates. With so many moving parts, manual oversight can quickly become overwhelming, error-prone, and time-consuming.&lt;/p&gt;

&lt;p&gt;That’s where automation steps in — not just as a convenience but as a necessity to keep your store running efficiently and at scale. While Shopify offers a rich ecosystem of apps and drag-and-drop interfaces, it often requires you to trade transparency and control for convenience.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’ll build a pipeline using GitHub Actions to export the latest products from the Shopify store, perform some actions using LLM, and update the products.&lt;/p&gt;

&lt;p&gt;Full source for the pipeline can be found &lt;a href="https://github.com/ankitpokhrel/shopctl/tree/main/examples/pipeline/product-enrichment" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Taking Back Control
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Let the robots worry about the boring stuff!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sooner or later, you will hit the limits with off-the-shelf apps and manual workflows and start looking for alternatives. One such alternative is to shift away from GUI-centric tools toward programmable pipelines that offer complete flexibility and control. What you want is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full ownership of your data&lt;/li&gt;
&lt;li&gt;Enhancements tailored to your brand and products&lt;/li&gt;
&lt;li&gt;Sharable Workflows: multiple stores could use the same workflow with little to no tweak&lt;/li&gt;
&lt;li&gt;Confidence in every step of the process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, let’s explore how we can build an automated CI pipeline to help mitigate the issues mentioned above. As a proof-of-concept, we’ll create a pipeline to streamline our product-content workflow. The pipeline will use LLM to review the latest products on our store, optimize the title, add SEO title and description, and generate a summary for the team to review.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;p&gt;Here’s what powers the workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://shopify.com/" rel="noopener noreferrer"&gt;Shopify&lt;/a&gt; — where our products live&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.github.com/en/actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt; — for orchestration and automation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ankitpokhrel/shopctl" rel="noopener noreferrer"&gt;ShopCTL&lt;/a&gt; — A command line utility for Shopify store management&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://openai.com/api/" rel="noopener noreferrer"&gt;OpenAI API&lt;/a&gt; — to revise product titles, generate SEO content, and suggestions&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.python.org/" rel="noopener noreferrer"&gt;Python&lt;/a&gt; and some &lt;a href="https://www.gnu.org/software/bash/" rel="noopener noreferrer"&gt;Bash&lt;/a&gt; scripts — for the enrichment logic and updates&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  First things first — Setting up the stack
&lt;/h2&gt;

&lt;p&gt;Let’s start by setting up a GitHub Actions workflow. We’ll store pipeline configs in the &lt;code&gt;.github/workflows/&lt;/code&gt; directory. Create a file named &lt;code&gt;enrich-products.yml&lt;/code&gt; inside the workflows directory. This file will define jobs for our product-content workflow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/enrich-products.yml&lt;/span&gt;

&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Shopify Product Enrichment&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;workflow_dispatch&lt;/code&gt; event in GitHub Actions allows you to manually trigger a workflow from the GitHub interface or via the API , or you can &lt;a href="https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#schedule" rel="noopener noreferrer"&gt;schedule it to run automatically&lt;/a&gt; at a specific time.&lt;/p&gt;

&lt;h4&gt;
  
  
  API Keys
&lt;/h4&gt;

&lt;p&gt;We’d need a few API keys to complete our configuration: &lt;code&gt;OPENAI_API_KEY&lt;/code&gt; for AI operations and &lt;code&gt;SHOPIFY_ACCESS_TOKEN&lt;/code&gt; to communicate with our store.&lt;/p&gt;

&lt;p&gt;Get the OpenAI API key from &lt;a href="https://platform.openai.com/api-keys" rel="noopener noreferrer"&gt;your OpenAI account&lt;/a&gt; and set it &lt;a href="https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions" rel="noopener noreferrer"&gt;as a secret in GitHub&lt;/a&gt;. Getting a Shopify access token is tricky since you need to create a dummy app to do so. Follow &lt;a href="https://www.shopify.com/partners/blog/17056443-how-to-generate-a-shopify-api-token" rel="noopener noreferrer"&gt;this official guide&lt;/a&gt; to get one.&lt;/p&gt;

&lt;h4&gt;
  
  
  ShopCTL
&lt;/h4&gt;

&lt;p&gt;We’ll use a &lt;a href="https://github.com/ankitpokhrel/shopctl" rel="noopener noreferrer"&gt;command-line tool&lt;/a&gt; to export and update our products. Let’s create a custom action that we can reuse to reference in our pipeline.&lt;/p&gt;

&lt;p&gt;Create a file called &lt;code&gt;setup-shopctl.yml&lt;/code&gt; inside actions directory and add the following config.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/actions/setup-shopctl.yml&lt;/span&gt;

&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup ShopCTL&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Installs Go and ShopCTL CLI&lt;/span&gt;
&lt;span class="na"&gt;runs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;using&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;composite"&lt;/span&gt;
  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Go&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-go@v5&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;go-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.24"&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install ShopCTL&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;sudo apt-get update&lt;/span&gt;
        &lt;span class="s"&gt;sudo apt-get install -y libx11-dev&lt;/span&gt;
        &lt;span class="s"&gt;go install github.com/ankitpokhrel/shopctl/cmd/shopctl@main&lt;/span&gt;
        &lt;span class="s"&gt;echo "$HOME/go/bin" &amp;gt;&amp;gt; "$GITHUB_PATH"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apart from custom actions, we need to add a configuration for the store we’re operating. Create a folder called &lt;code&gt;shopctl&lt;/code&gt; on the repo’s root and add the following config in a file called &lt;code&gt;.shopconfig.yml&lt;/code&gt;. Replace all occurrences of &lt;code&gt;store1&lt;/code&gt; with your store alias.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# shopctl/.shopcofig.yml&lt;/span&gt;

&lt;span class="na"&gt;ver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v0&lt;/span&gt;
&lt;span class="na"&gt;contexts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;alias&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;store1&lt;/span&gt;
      &lt;span class="na"&gt;store&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;store1.myshopify.com&lt;/span&gt;
&lt;span class="na"&gt;currentContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;store1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Finalizing the pipeline
&lt;/h2&gt;

&lt;p&gt;Our pipeline has four stages, viz: &lt;code&gt;Export -&amp;gt; Enrich -&amp;gt; Update -&amp;gt; Notify&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Stage 1: Export products
&lt;/h4&gt;

&lt;p&gt;The first step in our pipeline is to export the latest products from our store. Add a job called &lt;code&gt;export-products&lt;/code&gt; in the &lt;code&gt;enrich-products.yml&lt;/code&gt; file we created earlier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;export-products&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;SHOPIFY_ACCESS_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SHOPIFY_ACCESS_TOKEN }}&lt;/span&gt; &lt;span class="c1"&gt;# The secret we set earlier&lt;/span&gt;
      &lt;span class="na"&gt;SHOPIFY_CONFIG_HOME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.workspace }}&lt;/span&gt; &lt;span class="c1"&gt;# This will tell shopctl to use current dir to look for .shopconfig&lt;/span&gt;
    &lt;span class="na"&gt;outputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;has-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.check.outputs.has_data }}&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout repo&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup ShopCTL&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./.github/workflows/actions/setup-shopctl&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Export products&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;mkdir -p data&lt;/span&gt;

          &lt;span class="s"&gt;# Export latest data (last 7 days) using the shopctl tool as latest_products.tar.gz&lt;/span&gt;
          &lt;span class="s"&gt;shopctl export -r product="created_at:&amp;gt;=$(date -v -7d +%Y-%m-%d)" -o data/ -n latest_products -vvv&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check if export has data&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;check&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;if [ -s data/latest_products.tar.gz ]; then&lt;/span&gt;
            &lt;span class="s"&gt;echo "has_data=true" &amp;gt;&amp;gt; "$GITHUB_OUTPUT"&lt;/span&gt;
          &lt;span class="s"&gt;else&lt;/span&gt;
            &lt;span class="s"&gt;echo "has_data=false" &amp;gt;&amp;gt; "$GITHUB_OUTPUT"&lt;/span&gt;
            &lt;span class="s"&gt;echo "No products found to process"&lt;/span&gt;
          &lt;span class="s"&gt;fi&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload exported products&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.check.outputs.has_data == 'true'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exported-products&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;data/latest_products.tar.gz&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The job above will set up &lt;a href="https://github.com/ankitpokhrel/shopctl" rel="noopener noreferrer"&gt;ShopCTL&lt;/a&gt; using the custom action we created earlier. It will export all products created in the last 7 days and upload them as artifacts if any new products exist.&lt;/p&gt;

&lt;h4&gt;
  
  
  Stage 2a: Review catalog
&lt;/h4&gt;

&lt;p&gt;The next we want to do is to review our catalog. We’ll use OpenAI API to review product data samples and identify the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Issues or inconsistencies in tags, product types, or variants&lt;/li&gt;
&lt;li&gt;Missing or inconsistent inventory information&lt;/li&gt;
&lt;li&gt;Gaps in product configuration or variant structure&lt;/li&gt;
&lt;li&gt;Duplicate or overly similar products&lt;/li&gt;
&lt;li&gt;General recommendations to improve catalog quality and its completeness
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;review-catalog&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;export-products&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.OPENAI_API_KEY }}&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout repo&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download product export&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exported-products&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;data/&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Python&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.13"&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pip install openai&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run catalog review script&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;# Assuming your script is saved in scripts/review_catalog.py&lt;/span&gt;
          &lt;span class="s"&gt;python scripts/review_catalog.py \&lt;/span&gt;
            &lt;span class="s"&gt;data/latest_products.tar.gz \&lt;/span&gt;
            &lt;span class="s"&gt;data/review_summary.md&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload catalog summary&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;catalog-review-summary&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;data/review_summary.md&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Final summary&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "✅ Shopify product catalog review completed!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the &lt;code&gt;needs&lt;/code&gt; section. We want to run it after products are exported and made available as artifacts. We also need to set up Python, as our review script is written in Python. You can use any language of your choice here. The script generates &lt;code&gt;review_summary.md&lt;/code&gt;, which is uploaded as an artificat in the next step (example output below).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Identified Issues&lt;/span&gt;

&lt;span class="gu"&gt;### 1. Missing or Inconsistent Information:&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Some products have missing or inconsistent &lt;span class="sb"&gt;`productType`&lt;/span&gt; (e.g. &lt;span class="sb"&gt;`"gid://shopify/Product/8790718087392"`&lt;/span&gt;, &lt;span class="err"&gt;`&lt;/span&gt;"gid://shopify/Product/879071795632
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sample script and the prompt can be &lt;a href="https://github.com/ankitpokhrel/shopctl/blob/main/examples/pipeline/product-enrichment/scripts/review_catalog.py" rel="noopener noreferrer"&gt;found here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 2b: Enrich Products
&lt;/h3&gt;

&lt;p&gt;Similar to the &lt;code&gt;review-catalog&lt;/code&gt; job, add an &lt;a href="https://github.com/ankitpokhrel/shopctl/blob/main/examples/pipeline/product-enrichment/.github/workflows/enrich-products.yml#L83-L120" rel="noopener noreferrer"&gt;&lt;code&gt;enrich-products&lt;/code&gt; job&lt;/a&gt; that will run the script to review the product title and generate an SEO title and description for the product using OpenAI. This job runs in parallel with the review catalog job and generates a CSV with details on metadata to update.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm7aqu1kl6htff3uz8btu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm7aqu1kl6htff3uz8btu.png" alt="Generated enriched_products.csv file" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The sample script and the prompt can be &lt;a href="https://github.com/ankitpokhrel/shopctl/blob/main/examples/pipeline/product-enrichment/scripts/enrich_products.py" rel="noopener noreferrer"&gt;found here&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Stage 3: Update products
&lt;/h4&gt;

&lt;p&gt;Once the metadata is generated in stage 2b, we can update products using &lt;a href="https://github.com/ankitpokhrel/shopctl" rel="noopener noreferrer"&gt;ShopCTL&lt;/a&gt;. We’ll use a bash script instead of Python at this stage.&lt;/p&gt;

&lt;p&gt;Add a job called &lt;code&gt;update-products&lt;/code&gt;, as shown below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;update-products&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;enrich-products&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;SHOPIFY_ACCESS_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SHOPIFY_ACCESS_TOKEN }}&lt;/span&gt;
      &lt;span class="na"&gt;SHOPIFY_CONFIG_HOME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.workspace }}&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout repo&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup ShopCTL&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./.github/workflows/actions/setup-shopctl&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download enriched products&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;enriched-products&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;data/&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Apply updates using shopctl&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;mkdir -p logs&lt;/span&gt;
          &lt;span class="s"&gt;touch logs/audit.txt&lt;/span&gt;

          &lt;span class="s"&gt;while IFS=, read -r pid new_title seo_title seo_desc; do&lt;/span&gt;
            &lt;span class="s"&gt;# Strip leading/trailing quotes&lt;/span&gt;
            &lt;span class="s"&gt;seo_desc="${seo_desc%\"}"&lt;/span&gt;
            &lt;span class="s"&gt;seo_desc="${seo_desc#\"}"&lt;/span&gt;

            &lt;span class="s"&gt;# Use shopctl to update product details&lt;/span&gt;
            &lt;span class="s"&gt;if output=$(shopctl product update "$pid" \&lt;/span&gt;
                &lt;span class="s"&gt;--title "$new_title" \&lt;/span&gt;
                &lt;span class="s"&gt;--seo-title "$seo_title" \&lt;/span&gt;
                &lt;span class="s"&gt;--seo-desc "$seo_desc" 2&amp;gt;&amp;amp;1); then&lt;/span&gt;
                &lt;span class="s"&gt;echo "$pid,success" &amp;gt;&amp;gt; logs/audit.txt&lt;/span&gt;
            &lt;span class="s"&gt;else&lt;/span&gt;
              &lt;span class="s"&gt;sanitized_error=$(echo "$output" | tr '\n' ' ' | sed 's/,/ /g')&lt;/span&gt;
              &lt;span class="s"&gt;echo "$pid,failure,$sanitized_error" &amp;gt;&amp;gt; logs/audit.txt&lt;/span&gt;
            &lt;span class="s"&gt;fi&lt;/span&gt;
          &lt;span class="s"&gt;done &amp;lt; &amp;lt;(tail -n +2 data/enriched_products.csv)&lt;/span&gt;

        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload audit log&lt;/span&gt;
          &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
          &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;product-audit-log&lt;/span&gt;
            &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;logs/audit.txt&lt;/span&gt;

        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Final summary&lt;/span&gt;
          &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "✅ Shopify product enrichment and updates completed!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The job is relatively simple; it uses a bash script to read from the CSV file generated in the previous step, update the product using ShopCTL, and create a log file.&lt;/p&gt;

&lt;h4&gt;
  
  
  Stage 4: Notify
&lt;/h4&gt;

&lt;p&gt;Now, the only thing remaining is to notify interested parties that the job has been completed (or failed) and what has changed. You can either send a Slack notification or email the details. We will simply fetch and print the logs for the tutorial’s sake.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;notify&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;review-catalog&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;update-products&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download audit log&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;product-audit-log&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;logs/&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download catalog review&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;catalog-review-summary&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;data/&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Print audit summary&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;ls -lah logs/&lt;/span&gt;
          &lt;span class="s"&gt;ls -lah data/&lt;/span&gt;
          &lt;span class="s"&gt;echo "🧾 Shopify Product Update Audit"&lt;/span&gt;
          &lt;span class="s"&gt;echo "-------------------------------"&lt;/span&gt;

          &lt;span class="s"&gt;total=$(wc -l &amp;lt; logs/audit.txt)&lt;/span&gt;
          &lt;span class="s"&gt;updated=$(grep -c ',success' logs/audit.txt || true)&lt;/span&gt;
          &lt;span class="s"&gt;failed=$(grep -c ',failure' logs/audit.txt || true)&lt;/span&gt;

          &lt;span class="s"&gt;echo "✅ Success: $updated"&lt;/span&gt;
          &lt;span class="s"&gt;echo "❌ Failed: $failed"&lt;/span&gt;
          &lt;span class="s"&gt;echo "📦 Total Processed: $total"&lt;/span&gt;
          &lt;span class="s"&gt;echo ""&lt;/span&gt;
          &lt;span class="s"&gt;echo "📋 Detailed Audit:"&lt;/span&gt;
          &lt;span class="s"&gt;cat logs/audit.txt&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Print catalog review summary&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;echo ""&lt;/span&gt;
          &lt;span class="s"&gt;echo "🧠 Catalog Review Summary"&lt;/span&gt;
          &lt;span class="s"&gt;echo "-------------------------"&lt;/span&gt;
          &lt;span class="s"&gt;cat data/review_summary.md&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Putting It All Together
&lt;/h2&gt;

&lt;p&gt;The example above showcase how you can leverage available tools to create something unique and powerful tailored to your use case without handing over sensitive store data to external apps.&lt;/p&gt;

&lt;p&gt;While our proof-of-concept skips over a few production-grade essentials — like using a staging store for manual approvals and proper error handling — it gives you a general idea of how to get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;This level of flexibility and control opens up limitless possibilities — from automated A/B testing on product copies, multi-language enrichment workflows, dynamic pricing experiments, and automated inventory cleanup to personalized recommendations and beyond.&lt;/p&gt;

&lt;p&gt;With every step in your control, you can experiment with new ideas, adapt quickly to market shifts, and scale operations effortlessly as your business grows.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>githubactions</category>
      <category>shopify</category>
    </item>
    <item>
      <title>ShopCTL: A Developer-First Toolkit for Shopify Automation</title>
      <dc:creator>Konsole</dc:creator>
      <pubDate>Tue, 15 Apr 2025 21:11:00 +0000</pubDate>
      <link>https://dev.to/konsole/shopctl-a-developer-first-toolkit-for-shopify-automation-5hnl</link>
      <guid>https://dev.to/konsole/shopctl-a-developer-first-toolkit-for-shopify-automation-5hnl</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Shopify at your fingertips with shell automation&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Learning Shopify has been on my bucket list for a few years now. Plenty of people in my circle — friends, colleagues, and fellow devs — are all somehow involved with Shopify in one way or the other. Earlier this year, I finally had some breathing room between projects, so I figured it was the perfect time to give Shopify a proper look.&lt;/p&gt;

&lt;p&gt;I started exploring the platform by setting up a dev store, poking around the admin, and skimming through the API manual. While this was a quick and easy start, it didn’t give me a deeper understanding of the platform. Plus, clicking my way through the UI felt repetitive and tedious.&lt;/p&gt;

&lt;p&gt;That got me thinking: is there a more efficient, developer-centric way to manage a store? Something that I could run in a terminal, plug into a CI/CD pipeline, or script my way out of those mundane tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet ShopCTL
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ankitpokhrel" rel="noopener noreferrer"&gt;
        ankitpokhrel
      &lt;/a&gt; / &lt;a href="https://github.com/ankitpokhrel/shopctl" rel="noopener noreferrer"&gt;
        shopctl
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🛠️ [WiP] Manage Shopify store straight from the terminal
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div&gt;
    &lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;ShopCTL&lt;/h1&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
    &lt;p&gt;
        &lt;a href="https://github.com/ankitpokhrel/shopctl/actions?query=workflow%3Abuild+branch%3Amain" rel="noopener noreferrer"&gt;
            &lt;img alt="Build" src="https://camo.githubusercontent.com/5fb58328a50ec8f55036d0d03579678e1c8617c136b42d36ac122e44fbe9bbd5/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f616e6b6974706f6b6872656c2f73686f7063746c2f63692e796d6c3f6272616e63683d6d61696e267374796c653d666c61742d737175617265"&gt;
        &lt;/a&gt;
        &lt;a href="https://goreportcard.com/report/github.com/ankitpokhrel/shopctl" rel="nofollow noopener noreferrer"&gt;
            &lt;img alt="GO Report-card" src="https://camo.githubusercontent.com/c21194389f3c431799545f303e38617fa1e812108fcfd042993cb82abf29f909/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f616e6b6974706f6b6872656c2f73686f7063746c3f7374796c653d666c61742d737175617265"&gt;
        &lt;/a&gt;&lt;br&gt;
        &lt;a href="https://github.com/ankitpokhrel/shopctl#" rel="noopener noreferrer"&gt;&lt;img alt="Linux" src="https://camo.githubusercontent.com/ed3a846a16309032d2b6aaa9252207d625992ec815405ba7fc05ea2e54daf203/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c696e75782d2545322539432539332d6461726b2d2d677265656e3f6c6f676f3d6c696e7578266c6f676f436f6c6f723d7768697465267374796c653d666c61742d737175617265"&gt;&lt;/a&gt;
        &lt;a href="https://github.com/ankitpokhrel/shopctl#" rel="noopener noreferrer"&gt;&lt;img alt="macOS" src="https://camo.githubusercontent.com/bc287be7263596a4737ec26ab0429ed06a18092e3dd43edc578fee78fd289b76/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6d61634f532d2545322539432539332d6461726b2d2d677265656e3f6c6f676f3d6170706c65267374796c653d666c61742d737175617265"&gt;&lt;/a&gt;
        &lt;a href="https://github.com/ankitpokhrel/shopctl#" rel="noopener noreferrer"&gt;&lt;img alt="Windows" src="https://camo.githubusercontent.com/405dde298e9f1fc477e09f776906ba517018a0e1b7376fe03ffe17fffb5fa7c0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f57696e646f77732d25453225394325393357534c2d6461726b2d2d677265656e3f6c6f676f3d77696e646f7773267374796c653d666c61742d737175617265"&gt;&lt;/a&gt;
    &lt;/p&gt;
    &lt;p&gt;
        &lt;i&gt;[WiP] Command line Utility for Shopify Data Management&lt;/i&gt;
    &lt;/p&gt;
    &lt;a rel="noopener noreferrer" href="https://github.com/ankitpokhrel/shopctl/.github/assets/demo.gif"&gt;&lt;img alt="ShopCTL Demo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fankitpokhrel%2Fshopctl%2F.github%2Fassets%2Fdemo.gif"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;/div&gt;
&lt;p&gt;ShopCTL is a slightly opinionated, in-progress command-line utility for managing your Shopify store data. It comes with a handful of easy-to-compose commands
giving you a quick way to interact with your store's data straight from the terminal.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Table of Contents&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/shopctl#installation" rel="noopener noreferrer"&gt;Installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/shopctl#resources" rel="noopener noreferrer"&gt;Resources&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ankitpokhrel/shopctl#getting-started" rel="noopener noreferrer"&gt;Getting started&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/ankitpokhrel/shopctl#authentication" rel="noopener noreferrer"&gt;Authentication&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/shopctl#direct-access-token" rel="noopener noreferrer"&gt;Direct Access Token&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/shopctl#oauth" rel="noopener noreferrer"&gt;OAuth&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/shopctl#config-management" rel="noopener noreferrer"&gt;Config Management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/shopctl#shell-completion" rel="noopener noreferrer"&gt;Shell completion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/shopctl#usage" rel="noopener noreferrer"&gt;Usage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ankitpokhrel/shopctl#commands" rel="noopener noreferrer"&gt;Commands&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/shopctl#export" rel="noopener noreferrer"&gt;Export&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/shopctl#import" rel="noopener noreferrer"&gt;Import&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/shopctl#product" rel="noopener noreferrer"&gt;Product&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/shopctl#customer" rel="noopener noreferrer"&gt;Customer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/shopctl#webhook" rel="noopener noreferrer"&gt;Webhook&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/shopctl#development" rel="noopener noreferrer"&gt;Development&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a dummy app from the &lt;a href="https://partners.shopify.com/" rel="nofollow noopener noreferrer"&gt;Shopify Partners Dashboard&lt;/a&gt; and get the client ID and secret.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add &lt;code&gt;http://127.0.0.1/shopctl/auth/callback&lt;/code&gt; to the list of Allowed redirection URL(s)&lt;/li&gt;
&lt;li&gt;Make sure to request for &lt;a href="https://github.com/ankitpokhrel/shopctl/blob/main/internal/oauth/oauth.go#L35-L47" rel="noopener noreferrer"&gt;required scopes&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See &lt;a class="issue-link js-issue-link" href="https://github.com/ankitpokhrel/shopctl/discussions/3" rel="noopener noreferrer"&gt;#3&lt;/a&gt; for detailed instructions on how to setup an app for client ID and secret.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Export client ID and secret from the first step to your shell.&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;export&lt;/span&gt; SHOPCTL_CLIENT_ID=&lt;span class="pl-k"&gt;&amp;lt;&lt;/span&gt;client-id&lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-k"&gt;export&lt;/span&gt; SHOPCTL_CLIENT_SECRET=&lt;span class="pl-k"&gt;&amp;lt;&lt;/span&gt;client-secret&lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install the runnable binary to your &lt;code&gt;$GOPATH/bin&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;go&lt;/pre&gt;…
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ankitpokhrel/shopctl" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://github.com/ankitpokhrel/shopctl" rel="noopener noreferrer"&gt;ShopCTL&lt;/a&gt; is an in-progress command-line utility for managing your Shopify store data. It comes with a handful of easy-to-compose commands, giving you a quick way to interact with your store’s data straight from the terminal.&lt;/p&gt;

&lt;p&gt;Simply put, it’s like a Swiss Army knife for your Shopify store — giving you quick, scriptable commands to query and modify products, customers, and more (upcoming).&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Feature Highlights
&lt;/h3&gt;

&lt;p&gt;ShopCTL currently comes with product and customer-related commands. The tool is &lt;a href="https://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html" rel="noopener noreferrer"&gt;POSIX-compliant&lt;/a&gt;, giving you a familiarity with standard Unix command-line operations. The CLI flags are designed such that you can combine available flags in any order to create a unique query. For instance, the command below will give you all gift-cards on status DRAFT that were created after January 2025 and have tags on-sale and premium.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shopctl product list &lt;span class="nt"&gt;--gift-card&lt;/span&gt; &lt;span class="nt"&gt;-sDRAFT&lt;/span&gt; &lt;span class="nt"&gt;--tags&lt;/span&gt; on-sale,premium &lt;span class="nt"&gt;--created&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;=2025–01–01"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The list commands also accept queries in a &lt;a href="https://shopify.dev/docs/api/usage/search-syntax" rel="noopener noreferrer"&gt;Shopify Search query&lt;/a&gt; syntax as the first argument. That means you can build a complex search query as needed for your use case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List products using combination of raw query and available flags&lt;/span&gt;
shopctl product list &lt;span class="s2"&gt;"(title:Caramel Apple) OR (inventory_total:&amp;gt;500 inventory_total:&amp;lt;=1000)"&lt;/span&gt; &lt;span class="nt"&gt;--tags&lt;/span&gt; premium
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In addition to &lt;a href="https://github.com/ankitpokhrel/shopctl?tab=readme-ov-file#list" rel="noopener noreferrer"&gt;advanced searching&lt;/a&gt;, you can &lt;a href="https://github.com/ankitpokhrel/shopctl?tab=readme-ov-file#create" rel="noopener noreferrer"&gt;create&lt;/a&gt;, update, delete, &lt;a href="https://github.com/ankitpokhrel/shopctl?tab=readme-ov-file#export" rel="noopener noreferrer"&gt;export, and import&lt;/a&gt; products — including their options, variants, and media — and most of these features also apply to customers. A handy &lt;a href="https://github.com/ankitpokhrel/shopctl?tab=readme-ov-file#peek" rel="noopener noreferrer"&gt;peek&lt;/a&gt; command is available to give you a quick glance at product details right from your terminal. Support for other resources might be added in the future as needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automation and Scripting Use-cases
&lt;/h2&gt;

&lt;p&gt;Let’s explore some possible real-life use cases.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;True power in scripting isn’t just about automating tasks — it’s the belief that even the simplest code can create wonders. — Not me&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Seasonal pricing updates
&lt;/h3&gt;

&lt;p&gt;Seasonal sales are great for business, but updating prices for a whole collection can be tedious. A CLI tool could make it easy to script bulk price adjustments. For example, suppose you want to apply a 30% discount to all products tagged with “summer-sale” for a summer sale:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;product_id &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;shopctl product list &lt;span class="nt"&gt;--tags&lt;/span&gt; summer-sale &lt;span class="nt"&gt;--columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;--plain&lt;/span&gt; &lt;span class="nt"&gt;--no-headers&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;shopctl product variant list &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$product_id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;,price &lt;span class="nt"&gt;--plain&lt;/span&gt; &lt;span class="nt"&gt;--no-headers&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read &lt;/span&gt;variant_id price&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
        &lt;/span&gt;&lt;span class="nv"&gt;new_price&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$price&lt;/span&gt;&lt;span class="s2"&gt; * 0.7"&lt;/span&gt; | bc&lt;span class="si"&gt;)&lt;/span&gt;
        shopctl product variant edit &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$product_id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$variant_id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--price&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$new_price&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;done
done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Automated Inventory Discounts for Clearance
&lt;/h3&gt;

&lt;p&gt;Ever have excess inventory you want to clear out? With &lt;a href="https://github.com/ankitpokhrel/shopctl" rel="noopener noreferrer"&gt;ShopCTL&lt;/a&gt;, you can easily look for and flag or discount overstocked items. Let’s say any product with more than 100 units in stock should get a 20% price drop and a “clearance” tag. We can achieve this by filtering on inventory and then updating those products:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;product_id &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;shopctl product list &lt;span class="s2"&gt;"inventory_total:&amp;gt;=100"&lt;/span&gt; &lt;span class="nt"&gt;--columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;--plain&lt;/span&gt; &lt;span class="nt"&gt;--no-headers&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;shopctl product variant list &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$product_id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;,price &lt;span class="nt"&gt;--plain&lt;/span&gt; &lt;span class="nt"&gt;--no-headers&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read &lt;/span&gt;variant_id price&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
        &lt;/span&gt;&lt;span class="nv"&gt;new_price&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$price&lt;/span&gt;&lt;span class="s2"&gt; * 0.8"&lt;/span&gt; | bc&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# 20% discount&lt;/span&gt;
        shopctl product variant edit &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$product_id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$variant_id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--price&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$new_price&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;done
    &lt;/span&gt;shopctl product update &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$product_id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--tags&lt;/span&gt; &lt;span class="s2"&gt;"clearance"&lt;/span&gt; &lt;span class="c"&gt;# Add clearance tag&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Data Cleanup &amp;amp; Dynamic Tagging
&lt;/h3&gt;

&lt;p&gt;Data cleanup is another area where scripting shines. As your product catalog grows, keeping track of what’s selling and what’s just sitting in inventory becomes harder. Why not tag these products based on specific conditions, like high inventory and older creation dates, and act upon it without manually digging through product listings?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add 'slow-moving' tag to products with high inventory created before 2025&lt;/span&gt;
shopctl product list &lt;span class="s2"&gt;"inventory_total:&amp;gt;=500"&lt;/span&gt; &lt;span class="nt"&gt;--created&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;2025-01-01"&lt;/span&gt; &lt;span class="nt"&gt;--columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;--plain&lt;/span&gt; &lt;span class="nt"&gt;--no-headers&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | xargs &lt;span class="nt"&gt;-I&lt;/span&gt;&lt;span class="o"&gt;{}&lt;/span&gt; shopctl product update &lt;span class="o"&gt;{}&lt;/span&gt; &lt;span class="nt"&gt;--tags&lt;/span&gt; slow-moving
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another use-case could be to archive products with missing SKUs. Suppose you want to find any products that have missing SKUs (perhaps products that were created without proper variant SKUs) and archive them so they don’t clutter your active catalog. You can search for products with empty SKU fields and update their status in bulk with some simple bash scripts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;product_id &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;shopctl product list &lt;span class="nt"&gt;--sku&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="nt"&gt;--columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;--plain&lt;/span&gt; &lt;span class="nt"&gt;--no-headers&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;shopctl product update &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$product_id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--status&lt;/span&gt; archived
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How about something as simple as quickly finding and deleting stale DRAFT products to keep your catalog clean?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Remove all drafts older than 90 days&lt;/span&gt;
shopctl product list &lt;span class="nt"&gt;-sDRAFT&lt;/span&gt; — created “&amp;lt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-90d&lt;/span&gt; +%Y-%m-%d&lt;span class="si"&gt;)&lt;/span&gt;” &lt;span class="se"&gt;\&lt;/span&gt;
 | xargs &lt;span class="nt"&gt;-I&lt;/span&gt;&lt;span class="o"&gt;{}&lt;/span&gt; shopctl product delete &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Attaching Media to Products via CSV
&lt;/h3&gt;

&lt;p&gt;Attaching images or other media to products in bulk can be cumbersome through the web UI. Let’s say a client provides you a CSV file with relevant columns like a product ID/handle and a URL to an image that needs to be attached to that product. You can perform media attachments in bulk with a simple bash script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Sample csv&lt;/span&gt;
product_id,image_url,alt,media_type
12345,https://example.com/image1.jpg,Front view,image
12345,https://example.com/video1.mp4,Demo video,video
56789,https://example.com/image1.jpg,Front view,image

&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="c"&gt;# Skip the CSV header and process each row&lt;/span&gt;
&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; +2 images.csv | &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;, &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; product_id image_url alt media_type&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nv"&gt;media_type_upper&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;media_type&lt;/span&gt;&lt;span class="p"&gt;^^&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="c"&gt;# Convert media_type to uppercase&lt;/span&gt;
  shopctl product media attach &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$product_id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--url&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$image_url&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--alt&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$alt&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--media-type&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$media_type_upper&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Exporting and Importing Customer Data
&lt;/h3&gt;

&lt;p&gt;Last but not least, consider the task of exporting customer data for a marketing campaign or migrating customers to a new store. ShopCTL has you covered with built-in export/import for customers as well. For example, to export all customers from one store and import them into another:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shopctl &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; store1 &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nv"&gt;customer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"created:&amp;gt;2025-01-01 AND created:&amp;lt;2025-04-01"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /backups &lt;span class="nt"&gt;-n&lt;/span&gt; customers_this_quarter
shopctl import &lt;span class="nt"&gt;-c&lt;/span&gt; store2 &lt;span class="nt"&gt;-r&lt;/span&gt; customer &lt;span class="nt"&gt;-f&lt;/span&gt; /backups/customers_this_quarter.tar.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;The possibilities with scripting are endless — you can shape it to fit your own workflows and solve unique use cases. The examples above are just the start. As the tool matures, it will open the door to some advanced automation and workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn more
&lt;/h2&gt;

&lt;p&gt;Check out &lt;a href="https://github.com/ankitpokhrel/shopctl" rel="noopener noreferrer"&gt;the project page&lt;/a&gt; to view the full set of features and learn more about the project.&lt;/p&gt;

&lt;p&gt;Your suggestions and feedback are highly appreciated. Feel free to &lt;a href="https://github.com/ankitpokhrel/shopctl/discussions" rel="noopener noreferrer"&gt;start a discussion&lt;/a&gt; or &lt;a href="https://github.com/ankitpokhrel/shopctl/issues" rel="noopener noreferrer"&gt;create an issue&lt;/a&gt; to share your experience with the tool or to discuss a feature/issue. If you think this project is useful, consider contributing by starring the repo, sharing it with your friends, or submitting a PR.&lt;/p&gt;

</description>
      <category>devtools</category>
      <category>shopify</category>
      <category>go</category>
      <category>cli</category>
    </item>
    <item>
      <title>JiraCLI: Interactive command line for Atlassian Jira reached v1.0.0</title>
      <dc:creator>Konsole</dc:creator>
      <pubDate>Sun, 03 Jul 2022 19:33:24 +0000</pubDate>
      <link>https://dev.to/konsole/jiracli-interactive-command-line-for-atlassian-jira-reached-v100-b4c</link>
      <guid>https://dev.to/konsole/jiracli-interactive-command-line-for-atlassian-jira-reached-v100-b4c</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/ankitpokhrel/jira-cli" rel="noopener noreferrer"&gt;JiraCLI&lt;/a&gt; v1.0 supports a wide variety of new commands, authentication with &lt;a href="https://medium.com/r/?url=https%3A%2F%2Fconfluence.atlassian.com%2Fenterprise%2Fusing-personal-access-tokens-1026032365.html" rel="noopener noreferrer"&gt;personal access tokens (PAT)&lt;/a&gt;, OS keychain &amp;amp; &lt;a href="https://medium.com/r/?url=https%3A%2F%2Fwww.gnu.org%2Fsoftware%2Finetutils%2Fmanual%2Fhtml_node%2FThe-_002enetrc-file.html" rel="noopener noreferrer"&gt;.netrc&lt;/a&gt; for accessing credentials, custom fields, and many more.&lt;/p&gt;

&lt;p&gt;My goal with Jira CLI was to build a simple tool based on my personal Jira workflow that would allow me to seamlessly complete the most common developer workflows end-to-end in the terminal.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ankitpokhrel" rel="noopener noreferrer"&gt;
        ankitpokhrel
      &lt;/a&gt; / &lt;a href="https://github.com/ankitpokhrel/jira-cli" rel="noopener noreferrer"&gt;
        jira-cli
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🔥 Feature-rich interactive Jira command line.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div&gt;
    &lt;a href="https://github.com/ankitpokhrel/jira-cli#" rel="noopener noreferrer"&gt;
        &lt;img alt="stargazers over time" src="https://camo.githubusercontent.com/2df71223434fac9b3add74921992b9c7461a74f4f3a4188ebf0f74ba7e442658/68747470733a2f2f73746172732e6d6564762e696f2f616e6b6974706f6b6872656c2f6a6972612d636c692e737667"&gt;
    &lt;/a&gt;
    &lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;JiraCLI&lt;/h1&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
    &lt;p&gt;
        &lt;a href="https://github.com/ankitpokhrel/jira-cli/actions?query=workflow%3Abuild+branch%3Amaster" rel="noopener noreferrer"&gt;
            &lt;img alt="Build" src="https://camo.githubusercontent.com/16f14f6fb87b8b1ac38cf9601951fe8e2728c152542ce0f176a0287e555d08b4/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f616e6b6974706f6b6872656c2f6a6972612d636c692f63692e796d6c3f6272616e63683d6d61696e267374796c653d666c61742d737175617265"&gt;
        &lt;/a&gt;
        &lt;a href="https://goreportcard.com/report/github.com/ankitpokhrel/jira-cli" rel="nofollow noopener noreferrer"&gt;
            &lt;img alt="GO Report-card" src="https://camo.githubusercontent.com/8de84df7ee3b4c37207a6adea0f9c30df2a03ed3325ead549bf04772b5f1cb1d/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f616e6b6974706f6b6872656c2f6a6972612d636c693f7374796c653d666c61742d737175617265"&gt;
        &lt;/a&gt;
        &lt;a href="https://github.com/ankitpokhrel/jira-cli/blob/master/LICENSE" rel="noopener noreferrer"&gt;
            &lt;img alt="Software License" src="https://camo.githubusercontent.com/5ddd6787b46ff6b3a6e8bfa779dc451433a990e470ffe28b66c8fb4a3e5035ca/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d627269676874677265656e2e7376673f7374796c653d666c61742d737175617265"&gt;
        &lt;/a&gt;
        &lt;a href="https://github.com/ankitpokhrel/jira-cli#" rel="noopener noreferrer"&gt;
            &lt;img alt="Downloads" src="https://camo.githubusercontent.com/8312a4a3f30e682270e36a553c3e1530335825c907c93e0a9f7abb2725c21f9a/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f646f776e6c6f6164732f616e6b6974706f6b6872656c2f6a6972612d636c692f746f74616c3f7374796c653d666c61742d737175617265"&gt;
        &lt;/a&gt;
        &lt;a href="https://opencollective.com/jira-cli#backers" rel="nofollow noopener noreferrer"&gt;
            &lt;img alt="Financial Contributors" src="https://camo.githubusercontent.com/d9c39b48185756e3a8eda963f0f7d589f7119424ef2ecd5cad68259c49325f52/68747470733a2f2f696d672e736869656c64732e696f2f6f70656e636f6c6c6563746976652f6261636b6572732f6a6972612d636c693f7374796c653d666c61742d737175617265"&gt;
        &lt;/a&gt;
    &lt;/p&gt;
    &lt;p&gt;
        &lt;i&gt;Feature-rich Interactive Jira Command Line&lt;/i&gt;
    &lt;/p&gt;
    &lt;a rel="noopener noreferrer" href="https://github.com/ankitpokhrel/jira-cli.github/assets/demo.gif"&gt;&lt;img alt="JiraCLI Demo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fankitpokhrel%2Fjira-cli.github%2Fassets%2Fdemo.gif"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    &lt;p&gt;
         
            Financial support from private and corporate sponsors ensures the tool's continued development.&lt;br&gt;
            Please &lt;a href="https://opencollective.com/jira-cli#backers" rel="nofollow noopener noreferrer"&gt;consider sponsoring the project&lt;/a&gt; if you or your company rely on JiraCLI
         &lt;br&gt;&lt;br&gt;
        &lt;a href="https://opencollective.com/jira-cli#backers" rel="nofollow noopener noreferrer"&gt;
            &lt;img src="https://camo.githubusercontent.com/ff009ffd5a78f938be6e89ce736539c662f2a7151e2c564fa90160e2f718e0ae/68747470733a2f2f6f70656e636f6c6c6563746976652e636f6d2f6a6972612d636c692f6261636b6572732e737667" alt="jira-cli open collective badge"&gt;
        &lt;/a&gt;
    &lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
   &lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Supporters&lt;/h2&gt;
&lt;/div&gt;
   &lt;p&gt;
      &lt;a href="https://www.atlassian.com?from=ankitpokhrel/jira-cli" rel="nofollow noopener noreferrer"&gt;
         &lt;img alt="Atlassian Logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fankitpokhrel%2Fjira-cli.github%2Fassets%2Fsupporters%2Fatlassian.png"&gt;
      &lt;/a&gt;&lt;br&gt;
      &lt;a href="https://www.jetbrains.com/?from=ankitpokhrel/jira-cli" rel="nofollow noopener noreferrer"&gt;
         &lt;img alt="JetBrains Logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fankitpokhrel%2Fjira-cli.github%2Fassets%2Fsupporters%2Fjetbrains.png"&gt;
      &lt;/a&gt;
   &lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;JiraCLI is an interactive command line tool for Atlassian Jira that will help you avoid Jira UI to some extent. This
tool may not be able to do everything, but it has all the essential features required to improve your day-to-day workflow with Jira.&lt;/p&gt;
&lt;p&gt;The tool started with the idea of making issue search and navigation as straightforward as possible. However, with the
help of &lt;a href="https://github.com/ankitpokhrel/jira-cli#support-the-project" rel="noopener noreferrer"&gt;outstanding supporters like you&lt;/a&gt;, we evolved, and the tool now includes all necessary
features like issue creation, cloning, linking, ticket transition, and much more.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This tool is heavily inspired by the &lt;a href="https://github.com/cli/cli" rel="noopener noreferrer"&gt;GitHub CLI&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Supported platforms&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Note that some features might work slightly differently in cloud installation versus on-premise installation due to the…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ankitpokhrel/jira-cli" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The project started as an experiment over the weekends with the idea of making issue search and navigation as straightforward as possible. Over the course of several months, I switched teams/companies, worked with different kinds of workflows, and kept improving the tool to adapt to various use cases.&lt;/p&gt;

&lt;p&gt;The tool is far from perfect, and I would not consider it complete. But it has all the necessary features to improve your workflow with Jira. The app supports both cloud and on-premises Jira instances. Regardless of the differences between these installations, I've tried to keep the overall experience as similar as possible.&lt;/p&gt;

&lt;p&gt;The project has gained some traction over the past months, and I think this was a great time to release a stable version. I believe that this will allow everyone to integrate the tool into their daily workflows/pipelines confidently.&lt;/p&gt;

&lt;p&gt;Moving forward, the focus will be on fixing &lt;a href="https://medium.com/r/?url=https%3A%2F%2Fgithub.com%2Fankitpokhrel%2Fjira-cli%2Fissues" rel="noopener noreferrer"&gt;some of the long-standing bugs&lt;/a&gt;, adding more features, and revamping the TUI to add more actions. &lt;a href="https://medium.com/r/?url=https%3A%2F%2Fgithub.com%2Fankitpokhrel%2Fjira-cli%23support-the-project%3D" rel="noopener noreferrer"&gt;Any sort of contribution&lt;/a&gt; is heartily welcome.&lt;br&gt;
Lastly, huge thanks to all contributors. This would not have been possible without all the help.&lt;/p&gt;

&lt;p&gt;View the &lt;a href="https://medium.com/r/?url=https%3A%2F%2Fgithub.com%2Fankitpokhrel%2Fjira-cli%2Freleases%2Ftag%2Fv1.0.0" rel="noopener noreferrer"&gt;release notes&lt;/a&gt; or &lt;a href="https://github.com/ankitpokhrel/jira-cli" rel="noopener noreferrer"&gt;install and start using JiraCLI&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>go</category>
      <category>opensource</category>
      <category>productivity</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Jira CLI: New version adds support for on-premise jira installation</title>
      <dc:creator>Konsole</dc:creator>
      <pubDate>Mon, 01 Nov 2021 18:23:15 +0000</pubDate>
      <link>https://dev.to/konsole/jira-cli-new-version-adds-support-for-on-premise-jira-installation-2ok9</link>
      <guid>https://dev.to/konsole/jira-cli-new-version-adds-support-for-on-premise-jira-installation-2ok9</guid>
      <description>&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ankitpokhrel" rel="noopener noreferrer"&gt;
        ankitpokhrel
      &lt;/a&gt; / &lt;a href="https://github.com/ankitpokhrel/jira-cli" rel="noopener noreferrer"&gt;
        jira-cli
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🔥 Feature-rich interactive Jira command line.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div&gt;
    &lt;a href="https://github.com/ankitpokhrel/jira-cli#" rel="noopener noreferrer"&gt;
        &lt;img alt="stargazers over time" src="https://camo.githubusercontent.com/2df71223434fac9b3add74921992b9c7461a74f4f3a4188ebf0f74ba7e442658/68747470733a2f2f73746172732e6d6564762e696f2f616e6b6974706f6b6872656c2f6a6972612d636c692e737667"&gt;
    &lt;/a&gt;
    &lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;JiraCLI&lt;/h1&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
    &lt;p&gt;
        &lt;a href="https://github.com/ankitpokhrel/jira-cli/actions?query=workflow%3Abuild+branch%3Amaster" rel="noopener noreferrer"&gt;
            &lt;img alt="Build" src="https://camo.githubusercontent.com/16f14f6fb87b8b1ac38cf9601951fe8e2728c152542ce0f176a0287e555d08b4/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f616e6b6974706f6b6872656c2f6a6972612d636c692f63692e796d6c3f6272616e63683d6d61696e267374796c653d666c61742d737175617265"&gt;
        &lt;/a&gt;
        &lt;a href="https://goreportcard.com/report/github.com/ankitpokhrel/jira-cli" rel="nofollow noopener noreferrer"&gt;
            &lt;img alt="GO Report-card" src="https://camo.githubusercontent.com/8de84df7ee3b4c37207a6adea0f9c30df2a03ed3325ead549bf04772b5f1cb1d/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f616e6b6974706f6b6872656c2f6a6972612d636c693f7374796c653d666c61742d737175617265"&gt;
        &lt;/a&gt;
        &lt;a href="https://github.com/ankitpokhrel/jira-cli/blob/master/LICENSE" rel="noopener noreferrer"&gt;
            &lt;img alt="Software License" src="https://camo.githubusercontent.com/5ddd6787b46ff6b3a6e8bfa779dc451433a990e470ffe28b66c8fb4a3e5035ca/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d627269676874677265656e2e7376673f7374796c653d666c61742d737175617265"&gt;
        &lt;/a&gt;
        &lt;a href="https://github.com/ankitpokhrel/jira-cli#" rel="noopener noreferrer"&gt;
            &lt;img alt="Downloads" src="https://camo.githubusercontent.com/8312a4a3f30e682270e36a553c3e1530335825c907c93e0a9f7abb2725c21f9a/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f646f776e6c6f6164732f616e6b6974706f6b6872656c2f6a6972612d636c692f746f74616c3f7374796c653d666c61742d737175617265"&gt;
        &lt;/a&gt;
        &lt;a href="https://opencollective.com/jira-cli#backers" rel="nofollow noopener noreferrer"&gt;
            &lt;img alt="Financial Contributors" src="https://camo.githubusercontent.com/d9c39b48185756e3a8eda963f0f7d589f7119424ef2ecd5cad68259c49325f52/68747470733a2f2f696d672e736869656c64732e696f2f6f70656e636f6c6c6563746976652f6261636b6572732f6a6972612d636c693f7374796c653d666c61742d737175617265"&gt;
        &lt;/a&gt;
    &lt;/p&gt;
    &lt;p&gt;
        &lt;i&gt;Feature-rich Interactive Jira Command Line&lt;/i&gt;
    &lt;/p&gt;
    &lt;a rel="noopener noreferrer" href="https://github.com/ankitpokhrel/jira-cli.github/assets/demo.gif"&gt;&lt;img alt="JiraCLI Demo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fankitpokhrel%2Fjira-cli.github%2Fassets%2Fdemo.gif"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    &lt;p&gt;
         
            Financial support from private and corporate sponsors ensures the tool's continued development.&lt;br&gt;
            Please &lt;a href="https://opencollective.com/jira-cli#backers" rel="nofollow noopener noreferrer"&gt;consider sponsoring the project&lt;/a&gt; if you or your company rely on JiraCLI
         &lt;br&gt;&lt;br&gt;
        &lt;a href="https://opencollective.com/jira-cli#backers" rel="nofollow noopener noreferrer"&gt;
            &lt;img src="https://camo.githubusercontent.com/ff009ffd5a78f938be6e89ce736539c662f2a7151e2c564fa90160e2f718e0ae/68747470733a2f2f6f70656e636f6c6c6563746976652e636f6d2f6a6972612d636c692f6261636b6572732e737667" alt="jira-cli open collective badge"&gt;
        &lt;/a&gt;
    &lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
   &lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Supporters&lt;/h2&gt;
&lt;/div&gt;
   &lt;p&gt;
      &lt;a href="https://www.atlassian.com?from=ankitpokhrel/jira-cli" rel="nofollow noopener noreferrer"&gt;
         &lt;img alt="Atlassian Logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fankitpokhrel%2Fjira-cli.github%2Fassets%2Fsupporters%2Fatlassian.png"&gt;
      &lt;/a&gt;&lt;br&gt;
      &lt;a href="https://www.jetbrains.com/?from=ankitpokhrel/jira-cli" rel="nofollow noopener noreferrer"&gt;
         &lt;img alt="JetBrains Logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fankitpokhrel%2Fjira-cli.github%2Fassets%2Fsupporters%2Fjetbrains.png"&gt;
      &lt;/a&gt;
   &lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;JiraCLI is an interactive command line tool for Atlassian Jira that will help you avoid Jira UI to some extent. This
tool may not be able to do everything, but it has all the essential features required to improve your day-to-day workflow with Jira.&lt;/p&gt;
&lt;p&gt;The tool started with the idea of making issue search and navigation as straightforward as possible. However, with the
help of &lt;a href="https://github.com/ankitpokhrel/jira-cli#support-the-project" rel="noopener noreferrer"&gt;outstanding supporters like you&lt;/a&gt;, we evolved, and the tool now includes all necessary
features like issue creation, cloning, linking, ticket transition, and much more.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This tool is heavily inspired by the &lt;a href="https://github.com/cli/cli" rel="noopener noreferrer"&gt;GitHub CLI&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Supported platforms&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Note that some features might work slightly differently in cloud installation versus on-premise installation due to the…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ankitpokhrel/jira-cli" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;A new version of Jira CLI, v0.1.0, is now available with most requested support for the on-premise jira installation.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's Changed
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;feat: On-premise/local jira installation is almost &lt;a href="https://github.com/ankitpokhrel/jira-cli/issues/138" rel="noopener noreferrer"&gt;fully supported&lt;/a&gt; 🎉&lt;/li&gt;
&lt;li&gt;feat: Allow Tab to toggle focus between the sidebar and the contents screen&lt;/li&gt;
&lt;li&gt;feat: Command to generate UNIX style man pages for section 7&lt;/li&gt;
&lt;li&gt;fix: Cursor in terminal disappears if command exits with error&lt;/li&gt;
&lt;li&gt;Some minor enhancements, fixes and upgrades&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Full change log can be found in the &lt;a href="https://github.com/ankitpokhrel/jira-cli/releases/tag/v0.1.0" rel="noopener noreferrer"&gt;release page&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>go</category>
      <category>opensource</category>
      <category>showdev</category>
      <category>bash</category>
    </item>
    <item>
      <title>Jira CLI: The Missing Command-line Tool for Atlassian Jira</title>
      <dc:creator>Konsole</dc:creator>
      <pubDate>Sat, 16 Oct 2021 13:52:48 +0000</pubDate>
      <link>https://dev.to/konsole/jira-cli-the-missing-command-line-tool-for-atlassian-jira-2n2i</link>
      <guid>https://dev.to/konsole/jira-cli-the-missing-command-line-tool-for-atlassian-jira-2n2i</guid>
      <description>&lt;p&gt;&lt;em&gt;The &lt;a href="https://medium.com/@ankitpokhrel/introducing-jira-cli-the-missing-command-line-tool-for-atlassian-jira-fe44982cc1de" rel="noopener noreferrer"&gt;original version&lt;/a&gt; of this post first appeared in Medium.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The year is 2078 and there have been 0 improvements in the Jira UI — #rant&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Jira UI is terrible to work with. It is slow, buggy, and doesn’t even load on occasions. If you have to rely on it for your day-to-day job it is going to cost you a lot of time and frustration. These frustrations pile up if you need to create tickets, in a pre-defined format with proper labels, components, etc, for every change you make, even a typo!&lt;/p&gt;

&lt;p&gt;Since I spend a significant amount of my time in the command-line, this is my small attempt to make Jira experience better for the CLI users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Introducing Jira CLI
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ankitpokhrel" rel="noopener noreferrer"&gt;
        ankitpokhrel
      &lt;/a&gt; / &lt;a href="https://github.com/ankitpokhrel/jira-cli" rel="noopener noreferrer"&gt;
        jira-cli
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🔥 Feature-rich interactive Jira command line.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div&gt;
    &lt;a href="https://github.com/ankitpokhrel/jira-cli#" rel="noopener noreferrer"&gt;
        &lt;img alt="stargazers over time" src="https://camo.githubusercontent.com/2df71223434fac9b3add74921992b9c7461a74f4f3a4188ebf0f74ba7e442658/68747470733a2f2f73746172732e6d6564762e696f2f616e6b6974706f6b6872656c2f6a6972612d636c692e737667"&gt;
    &lt;/a&gt;
    &lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;JiraCLI&lt;/h1&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
    &lt;p&gt;
        &lt;a href="https://github.com/ankitpokhrel/jira-cli/actions?query=workflow%3Abuild+branch%3Amaster" rel="noopener noreferrer"&gt;
            &lt;img alt="Build" src="https://camo.githubusercontent.com/16f14f6fb87b8b1ac38cf9601951fe8e2728c152542ce0f176a0287e555d08b4/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f616e6b6974706f6b6872656c2f6a6972612d636c692f63692e796d6c3f6272616e63683d6d61696e267374796c653d666c61742d737175617265"&gt;
        &lt;/a&gt;
        &lt;a href="https://goreportcard.com/report/github.com/ankitpokhrel/jira-cli" rel="nofollow noopener noreferrer"&gt;
            &lt;img alt="GO Report-card" src="https://camo.githubusercontent.com/8de84df7ee3b4c37207a6adea0f9c30df2a03ed3325ead549bf04772b5f1cb1d/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f616e6b6974706f6b6872656c2f6a6972612d636c693f7374796c653d666c61742d737175617265"&gt;
        &lt;/a&gt;
        &lt;a href="https://github.com/ankitpokhrel/jira-cli/blob/master/LICENSE" rel="noopener noreferrer"&gt;
            &lt;img alt="Software License" src="https://camo.githubusercontent.com/5ddd6787b46ff6b3a6e8bfa779dc451433a990e470ffe28b66c8fb4a3e5035ca/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d627269676874677265656e2e7376673f7374796c653d666c61742d737175617265"&gt;
        &lt;/a&gt;
        &lt;a href="https://github.com/ankitpokhrel/jira-cli#" rel="noopener noreferrer"&gt;
            &lt;img alt="Downloads" src="https://camo.githubusercontent.com/8312a4a3f30e682270e36a553c3e1530335825c907c93e0a9f7abb2725c21f9a/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f646f776e6c6f6164732f616e6b6974706f6b6872656c2f6a6972612d636c692f746f74616c3f7374796c653d666c61742d737175617265"&gt;
        &lt;/a&gt;
        &lt;a href="https://opencollective.com/jira-cli#backers" rel="nofollow noopener noreferrer"&gt;
            &lt;img alt="Financial Contributors" src="https://camo.githubusercontent.com/d9c39b48185756e3a8eda963f0f7d589f7119424ef2ecd5cad68259c49325f52/68747470733a2f2f696d672e736869656c64732e696f2f6f70656e636f6c6c6563746976652f6261636b6572732f6a6972612d636c693f7374796c653d666c61742d737175617265"&gt;
        &lt;/a&gt;
    &lt;/p&gt;
    &lt;p&gt;
        &lt;i&gt;Feature-rich Interactive Jira Command Line&lt;/i&gt;
    &lt;/p&gt;
    &lt;a rel="noopener noreferrer" href="https://github.com/ankitpokhrel/jira-cli.github/assets/demo.gif"&gt;&lt;img alt="JiraCLI Demo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fankitpokhrel%2Fjira-cli.github%2Fassets%2Fdemo.gif"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    &lt;p&gt;
         
            Financial support from private and corporate sponsors ensures the tool's continued development.&lt;br&gt;
            Please &lt;a href="https://opencollective.com/jira-cli#backers" rel="nofollow noopener noreferrer"&gt;consider sponsoring the project&lt;/a&gt; if you or your company rely on JiraCLI
         &lt;br&gt;&lt;br&gt;
        &lt;a href="https://opencollective.com/jira-cli#backers" rel="nofollow noopener noreferrer"&gt;
            &lt;img src="https://camo.githubusercontent.com/ff009ffd5a78f938be6e89ce736539c662f2a7151e2c564fa90160e2f718e0ae/68747470733a2f2f6f70656e636f6c6c6563746976652e636f6d2f6a6972612d636c692f6261636b6572732e737667" alt="jira-cli open collective badge"&gt;
        &lt;/a&gt;
    &lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
   &lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Supporters&lt;/h2&gt;
&lt;/div&gt;
   &lt;p&gt;
      &lt;a href="https://www.atlassian.com?from=ankitpokhrel/jira-cli" rel="nofollow noopener noreferrer"&gt;
         &lt;img alt="Atlassian Logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fankitpokhrel%2Fjira-cli.github%2Fassets%2Fsupporters%2Fatlassian.png"&gt;
      &lt;/a&gt;&lt;br&gt;
      &lt;a href="https://www.jetbrains.com/?from=ankitpokhrel/jira-cli" rel="nofollow noopener noreferrer"&gt;
         &lt;img alt="JetBrains Logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fankitpokhrel%2Fjira-cli.github%2Fassets%2Fsupporters%2Fjetbrains.png"&gt;
      &lt;/a&gt;
   &lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;JiraCLI is an interactive command line tool for Atlassian Jira that will help you avoid Jira UI to some extent. This
tool may not be able to do everything, but it has all the essential features required to improve your day-to-day workflow with Jira.&lt;/p&gt;
&lt;p&gt;The tool started with the idea of making issue search and navigation as straightforward as possible. However, with the
help of &lt;a href="https://github.com/ankitpokhrel/jira-cli#support-the-project" rel="noopener noreferrer"&gt;outstanding supporters like you&lt;/a&gt;, we evolved, and the tool now includes all necessary
features like issue creation, cloning, linking, ticket transition, and much more.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This tool is heavily inspired by the &lt;a href="https://github.com/cli/cli" rel="noopener noreferrer"&gt;GitHub CLI&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Supported platforms&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Note that some features might work slightly differently in cloud installation versus on-premise installation due to the…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ankitpokhrel/jira-cli" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  TLDR; Features Highlight
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Interactive mode + an option to easily integrate with shell/automation scripts using standard &lt;a href="https://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html" rel="noopener noreferrer"&gt;POSIX-complaint&lt;/a&gt; flags.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ankitpokhrel/jira-cli/#list" rel="noopener noreferrer"&gt;Easy search&lt;/a&gt; and navigation. For instance, you can easily search for something like “&lt;em&gt;Issues that are of high priority, is in progress, was created this month, and has a label called backend&lt;/em&gt;” with &lt;code&gt;jira issue list -yHigh -s"In Progress" --created month -lbackend&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ankitpokhrel/jira-cli/#create" rel="noopener noreferrer"&gt;Create a neat Jira ticket&lt;/a&gt; (and &lt;a href="https://github.com/ankitpokhrel/jira-cli/#comment" rel="noopener noreferrer"&gt;comment&lt;/a&gt;) using &lt;a href="https://github.github.com/gfm/" rel="noopener noreferrer"&gt;Github-flavored&lt;/a&gt; + &lt;a href="https://jira.atlassian.com/secure/WikiRendererHelpAction.jspa?section=all" rel="noopener noreferrer"&gt;Jira-flavored&lt;/a&gt; markdown as a template. Supports pre-defined templates.&lt;/li&gt;
&lt;li&gt;The ticket details are translated to &lt;a href="https://github.github.com/gfm/" rel="noopener noreferrer"&gt;markdown&lt;/a&gt; from the &lt;a href="https://developer.atlassian.com/cloud/jira/platform/apis/document/structure/" rel="noopener noreferrer"&gt;Atlassian document&lt;/a&gt; and is &lt;a href="https://github.com/ankitpokhrel/jira-cli/#view" rel="noopener noreferrer"&gt;beautifully displayed&lt;/a&gt; on the screen when you view it.&lt;/li&gt;
&lt;li&gt;Easy &lt;a href="https://github.com/ankitpokhrel/jira-cli/#sprint" rel="noopener noreferrer"&gt;sprint&lt;/a&gt; and &lt;a href="https://github.com/ankitpokhrel/jira-cli/#epic" rel="noopener noreferrer"&gt;epic&lt;/a&gt; navigation. You can quickly view tickets in previous, current, and next sprint tickets using flags like &lt;code&gt;--prev&lt;/code&gt;, &lt;code&gt;--next&lt;/code&gt;, and &lt;code&gt;--current&lt;/code&gt; eg: &lt;code&gt;jira sprint list --current&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Fast and straightforward &lt;a href="https://github.com/ankitpokhrel/jira-cli/#clone" rel="noopener noreferrer"&gt;ticket cloning&lt;/a&gt; with the ability to replace text in summary and description.&lt;/li&gt;
&lt;li&gt;You can &lt;a href="https://github.com/ankitpokhrel/jira-cli#edit" rel="noopener noreferrer"&gt;edit&lt;/a&gt;, &lt;a href="https://github.com/ankitpokhrel/jira-cli#link" rel="noopener noreferrer"&gt;link&lt;/a&gt;, &lt;a href="https://github.com/ankitpokhrel/jira-cli#assign" rel="noopener noreferrer"&gt;assign&lt;/a&gt; and &lt;a href="https://github.com/ankitpokhrel/jira-cli#movetransition" rel="noopener noreferrer"&gt;transition&lt;/a&gt; the issues with ease.&lt;/li&gt;
&lt;li&gt;Supports multiple Jira servers using &lt;code&gt;--config&lt;/code&gt; flag or &lt;code&gt;XDG_CONFIG_HOME&lt;/code&gt; env.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Disclaimer
&lt;/h3&gt;

&lt;p&gt;The tool is mosty tested with the latest Jira cloud since that’s what I usually work with. However, the support for on-premise Jira installation is &lt;a href="https://github.com/ankitpokhrel/jira-cli/issues/138" rel="noopener noreferrer"&gt;on the way&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also, the tool is still a work in progress with some exciting features in the todo pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Searching for an Issue
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/ankitpokhrel/jira-cli" rel="noopener noreferrer"&gt;JiraCLI&lt;/a&gt; makes searching for an issue as easy as it should be. The lists are displayed in an interactive UI and can be navigated easily to perform actions like viewing, navigating, and copying issue keys/links.&lt;/p&gt;

&lt;p&gt;The examples below shows how easy it is to look for an issue. See more examples &lt;a href="https://github.com/ankitpokhrel/jira-cli" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# List issues that I am watching in the current board
$ jira issue list -w

# List issues assigned to me
$ jira issue list -a$(jira me)

# List issues created within an hour and updated in the last 30 minutes️
$ jira issue list --created -1h --updated -30m

# Give me issues that are of high priority, is in progress, was created this month, and has given labels 🔥
$ jira issue list -yHigh -s"In Progress" --created month -lbackend -l"high prio"

# Wait, what was that ticket I opened earlier today? 😫
$ jira issue list --history# What was the first issue I ever reported on the current board? 🤔
$ jira issue list -r$(jira me) --reverse

# What was the first bug I ever fixed in the current board? 🐞
$ jira issue list -a$(jira me) -tBug sDone -rFixed --reverse

# What issues did I report this week? 🤷‍♂️
$ jira issue list -r$(jira me) --created week
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating an Issue
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;create&lt;/code&gt; command lets you create an issue and supports Github-flavored and Jira-flavored markdown for writing descriptions. You can load pre-defined templates using &lt;code&gt;--template&lt;/code&gt; flag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Create an issue using interactive prompt
$ jira issue create

# Pass required parameters to skip prompt or use --no-input option
$ jira issue create -tBug -s"New Bug" -yHigh -lbug -lurgent -b"Bug description"

# Load description from template file
$ jira issue create --template /path/to/template.tmpl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fym39alzg33oo08zsgz9x.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fym39alzg33oo08zsgz9x.gif" alt="Creating an issue" width="1024" height="1024"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The preview below shows the markdown template passed in JiraCLI and the way it is rendered in the Jira UI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feerlt17muf76ybavceiw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feerlt17muf76ybavceiw.jpeg" alt="Create shell and UI preview" width="800" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Viewing an Issue
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;view&lt;/code&gt; command lets you see issue details in a terminal. Atlassian document is roughly converted to a markdown and is nicely displayed in the terminal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ jira issue view ISSUE-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3end26nykfs5ok4ad8ip.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3end26nykfs5ok4ad8ip.gif" alt="Viewing an issue" width="1024" height="1024"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Assigning a user to an issue
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;assign&lt;/code&gt; command lets you easily assign and unassign users to and from the issue.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn8wx3wkd5y7y0007l4vl.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn8wx3wkd5y7y0007l4vl.gif" alt="Assigning a user to an issue" width="" height=""&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Assign user to an issue using interactive prompt
$ jira issue assign

# Pass required parameters to skip prompt
$ jira issue assign ISSUE-1 "Jon Doe"

# Assign to self
$ jira issue assign ISSUE-1 $(jira me)

# Will prompt for selection if keyword suffix returns multiple entries
$ jira issue assign ISSUE-1 suffix

# Assign to default assignee
$ jira issue assign ISSUE-1 default

# Unassign
$ jira issue assign ISSUE-1 x
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cloning an Issue
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;clone&lt;/code&gt; command lets you clone an issue. You can update fields like summary, priority, assignee, labels, and components when cloning the issue. The command also allows you to replace a part of the string (case-sensitive) in summary and description using &lt;code&gt;--replace/-H&lt;/code&gt; option.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Clone an issue
$ jira issue clone ISSUE-1

# Clone issue and modify the summary, priority and assignee
$ jira issue clone ISSUE-1 -s"Modified summary" -yHigh -a$(jira me)

# Clone issue and replace text from summary and description
$ jira issue clone ISSUE-1 -H"find-me:replace-with-me"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sprints
&lt;/h3&gt;

&lt;p&gt;Sprints are displayed in an explorer view by default. You can output the results in a table view using the &lt;code&gt;--table&lt;/code&gt; flag. When viewing sprint issues, you can use all filters available for the issue command. You can quickly view tickets in previous, current, and next sprint tickets using flags like &lt;code&gt;--prev&lt;/code&gt;, &lt;code&gt;--next&lt;/code&gt;, and &lt;code&gt;--current&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4pix64v7hkeaia5w7wlx.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4pix64v7hkeaia5w7wlx.gif" alt="Sprints" width="1024" height="1024"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Display sprints in an interactive list
$ jira sprint list

# Display tickets in the current active sprint
$ jira sprint list --current

# Display tickets in the previous sprint
$ jira sprint list --prev

# Display tickets of a particular sprint
$ jira sprint list &amp;lt;SPRINT_ID&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Learn more
&lt;/h3&gt;

&lt;p&gt;Check out &lt;a href="https://github.com/ankitpokhrel/jira-cli" rel="noopener noreferrer"&gt;the project page&lt;/a&gt; to view the full set of features and learn more about the project.&lt;/p&gt;

&lt;p&gt;Your suggestions and feedback is highly appreciated. Feel free to &lt;a href="https://github.com/ankitpokhrel/jira-cli/discussions" rel="noopener noreferrer"&gt;start a discussion&lt;/a&gt; or &lt;a href="https://github.com/ankitpokhrel/jira-cli/issues/new" rel="noopener noreferrer"&gt;create an issue&lt;/a&gt; to share your experience about the tool or to discuss a feature/issue. If you think this project is useful, consider contributing by &lt;a href="https://github.com/ankitpokhrel/jira-cli/stargazers" rel="noopener noreferrer"&gt;starring the repo&lt;/a&gt;, sharing with your friends, or submitting a PR.&lt;/p&gt;

</description>
      <category>go</category>
      <category>opensource</category>
      <category>showdev</category>
      <category>bash</category>
    </item>
    <item>
      <title>Resumable file upload in PHP: Handle large file uploads in an elegant way</title>
      <dc:creator>Konsole</dc:creator>
      <pubDate>Sun, 02 Dec 2018 10:33:39 +0000</pubDate>
      <link>https://dev.to/konsole/resumable-file-upload-in-php-handle-large-file-uploads-in-an-elegant-way-4a84</link>
      <guid>https://dev.to/konsole/resumable-file-upload-in-php-handle-large-file-uploads-in-an-elegant-way-4a84</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Ever struggled with large file upload in PHP? Wondered if you could continue uploading where you left off without re-uploading whole data again in case of any interruptions? If this sounds familiar to you, then keep reading.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;File upload is a common task we do in almost all of our modern web projects. With all different tools available, it is not that hard to implement file upload feature in any language. But, still, when it comes to large file upload things gets a bit complicated.&lt;/p&gt;

&lt;p&gt;Say, you are trying to upload a fairly large file. You have been waiting for more than an hour already and the upload is at 90%. Then suddenly, your connection drops or browser crashed. The upload is aborted and you need to start from the beginning. Frustrating, isn't it? Even worse, if you are in a slow connection, like many places in the world, no matter how often you try you will only be able to upload first part of the upload every time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F83sa9d9ujdvd13cjo9dm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F83sa9d9ujdvd13cjo9dm.png" alt="Basic TUS architecture" width="300" height="225"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this post, we will see an attempt to solve this problem in PHP by uploading files in resumable chunks using tus protocol.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/sHep0dLbMd4"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  What is tus?
&lt;/h2&gt;

&lt;p&gt;Tus is a HTTP based &lt;a href="https://tus.io/" rel="noopener noreferrer"&gt;open protocol for resumable file uploads&lt;/a&gt;. Resumable means we can carry on where we left off without re-uploading whole data again in case of any interruptions. An interruption may happen willingly if the user wants to pause, or by accident in case of a network issue or server outage.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tus protocol was &lt;a href="https://medium.com/vimeo-engineering-blog/vimeo-is-adopting-tus-d5e999acd517" rel="noopener noreferrer"&gt;adopted by Vimeo&lt;/a&gt; in May 2017.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why tus?
&lt;/h2&gt;

&lt;p&gt;Quoting from &lt;a href="https://medium.com/vimeo-engineering-blog/vimeo-is-adopting-tus-d5e999acd517" rel="noopener noreferrer"&gt;Vimeo's blog&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We decided to use tus in our upload stack because the tus protocol standardizes the process of uploading files in a concise and open manner. This standardization will allow API developers to focus more on their application-specific code, and less on the upload process itself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Another main benefit of uploading a file this way is that you can start uploading from a laptop and even continue uploading the same file from your mobile or any other device. This is a great way to enhance your user experience.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhwjgayxrph5x6d5wlqq2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhwjgayxrph5x6d5wlqq2.png" alt="Basic Tus Architecture" width="800" height="208"&gt;&lt;/a&gt;&lt;/p&gt;
Basic Tus Architecture



&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Let's start by adding our dependency.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;composer require ankitpokhrel/tus-php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://github.com/ankitpokhrel/tus-php" rel="noopener noreferrer"&gt;tus-php&lt;/a&gt; is a framework agnostic pure PHP &lt;a href="https://tus.io/implementations.html" rel="noopener noreferrer"&gt;server and client implementation&lt;/a&gt; for the tus resumable upload protocol v1.0.0.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ankitpokhrel" rel="noopener noreferrer"&gt;
        ankitpokhrel
      &lt;/a&gt; / &lt;a href="https://github.com/ankitpokhrel/tus-php" rel="noopener noreferrer"&gt;
        tus-php
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🚀 A pure PHP server and client for the tus resumable upload protocol v1.0.0
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;TusPHP&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;
    &lt;a href="https://packagist.org/packages/ankitpokhrel/tus-php" rel="nofollow noopener noreferrer"&gt;
        &lt;img alt="PHP Version" src="https://camo.githubusercontent.com/a9ba83fec13b4c5b10936af234b44ded83810769f14de3b79b673b973b9d3a4d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d382e312532422d627269676874677265656e2e7376673f7374796c653d666c61742d737175617265"&gt;
    &lt;/a&gt;
    &lt;a href="https://github.com/ankitpokhrel/tus-php/actions/workflows/ci.yml?query=branch%3Amain+is%3Acompleted" rel="noopener noreferrer"&gt;
        &lt;img alt="Build Status" src="https://camo.githubusercontent.com/7410f2c718b84ecad347e9ed3f966bf91655a34fb520e8755f29a340c083c8a1/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f616e6b6974706f6b6872656c2f7475732d7068702f63692e796d6c3f6272616e63683d6d61696e267374796c653d666c61742d737175617265"&gt;
    &lt;/a&gt;
    &lt;a href="https://scrutinizer-ci.com/g/ankitpokhrel/tus-php" rel="nofollow noopener noreferrer"&gt;
        &lt;img alt="Code Coverage" src="https://camo.githubusercontent.com/d156c67017f1220008fcf0a9e208b5ee9fbcf671f23cb8f6645fa5936ca79119/68747470733a2f2f696d672e736869656c64732e696f2f7363727574696e697a65722f636f7665726167652f672f616e6b6974706f6b6872656c2f7475732d7068702e7376673f7374796c653d666c61742d737175617265"&gt;
    &lt;/a&gt;
    &lt;a href="https://scrutinizer-ci.com/g/ankitpokhrel/tus-php" rel="nofollow noopener noreferrer"&gt;
        &lt;img alt="Scrutinizer Code Quality" src="https://camo.githubusercontent.com/beaa56a9866b5439e829f168d4d8c4dc45c6697c1c41c364e40353f2dea0e7b7/68747470733a2f2f696d672e736869656c64732e696f2f7363727574696e697a65722f672f616e6b6974706f6b6872656c2f7475732d7068702e7376673f7374796c653d666c61742d737175617265"&gt;
    &lt;/a&gt;
    &lt;a href="https://packagist.org/packages/ankitpokhrel/tus-php" rel="nofollow noopener noreferrer"&gt;
        &lt;img alt="Downloads" src="https://camo.githubusercontent.com/3c0336314dee8030af919aa795e4537394e418dae9bf45cc1aa7471a0e1a7159/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646d2f616e6b6974706f6b6872656c2f7475732d7068702e7376673f7374796c653d666c61742d737175617265"&gt;
    &lt;/a&gt;
    &lt;a href="https://github.com/ankitpokhrel/tus-php/blob/main/LICENSE" rel="noopener noreferrer"&gt;
        &lt;img alt="Software License" src="https://camo.githubusercontent.com/5ddd6787b46ff6b3a6e8bfa779dc451433a990e470ffe28b66c8fb4a3e5035ca/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d627269676874677265656e2e7376673f7374796c653d666c61742d737175617265"&gt;
    &lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;Resumable file upload in PHP using &lt;a href="https://tus.io" rel="nofollow noopener noreferrer"&gt;tus resumable upload protocol v1.0.0&lt;/a&gt;&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;a rel="noopener noreferrer" href="https://github.com/ankitpokhrel/tus-php/blob/main/example/demo.gif"&gt;&lt;img alt="TusPHP Demo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fankitpokhrel%2Ftus-php%2Fraw%2Fmain%2Fexample%2Fdemo.gif"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    &lt;a href="https://medium.com/@ankitpokhrel/resumable-file-upload-in-php-handle-large-file-uploads-in-an-elegant-way-e6c6dfdeaedb" rel="nofollow noopener noreferrer"&gt;Medium Article&lt;/a&gt; ⚡ &lt;a href="https://github.com/ankitpokhrel/tus-php/wiki/Laravel-&amp;amp;-Lumen-Integration" rel="noopener noreferrer"&gt;Laravel &amp;amp; Lumen Integration&lt;/a&gt; ⚡ &lt;a href="https://github.com/ankitpokhrel/tus-php/wiki/Symfony-Integration" rel="noopener noreferrer"&gt;Symfony Integration&lt;/a&gt; ⚡ &lt;a href="https://github.com/ankitpokhrel/tus-php/wiki/CakePHP-Integration" rel="noopener noreferrer"&gt;CakePHP Integration&lt;/a&gt; ⚡ &lt;a href="https://github.com/ankitpokhrel/tus-php/wiki/WordPress-Integration" rel="noopener noreferrer"&gt;WordPress Integration&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;a href="https://opencollective.com/tus-php#backers" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/23808e278113f15fd442b40d3d91b2c33b8d03b471c656569d379343b15a7345/68747470733a2f2f6f70656e636f6c6c6563746976652e636f6d2f7475732d7068702f6261636b6572732e737667"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;tus&lt;/strong&gt; is a HTTP based protocol for resumable file uploads. Resumable means you can carry on where you left off without
re-uploading whole data again in case of any interruptions. An interruption may happen willingly if the user wants
to pause, or by accident in case of a network issue or server outage.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Table of Contents&lt;/h3&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/tus-php#installation" rel="noopener noreferrer"&gt;Installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ankitpokhrel/tus-php#usage" rel="noopener noreferrer"&gt;Usage&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/ankitpokhrel/tus-php#server" rel="noopener noreferrer"&gt;Server&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/tus-php#nginx" rel="noopener noreferrer"&gt;Nginx&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/tus-php#apache" rel="noopener noreferrer"&gt;Apache&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/tus-php#client" rel="noopener noreferrer"&gt;Client&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/tus-php#third-party-client-libraries" rel="noopener noreferrer"&gt;Third Party Client Libraries&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/tus-php#cloud-providers" rel="noopener noreferrer"&gt;Cloud Providers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ankitpokhrel/tus-php#extension-support" rel="noopener noreferrer"&gt;Extension support&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/tus-php#expiration" rel="noopener noreferrer"&gt;Expiration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/tus-php#concatenation" rel="noopener noreferrer"&gt;Concatenation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ankitpokhrel/tus-php#events" rel="noopener noreferrer"&gt;Events&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/tus-php#responding-to-an-event" rel="noopener noreferrer"&gt;Responding to an Event&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ankitpokhrel/tus-php#middleware" rel="noopener noreferrer"&gt;Middleware&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/tus-php#creating-a-middleware" rel="noopener noreferrer"&gt;Creating a Middleware&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/tus-php#adding-a-middleware" rel="noopener noreferrer"&gt;Adding a Middleware&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/tus-php#skipping-a-middleware" rel="noopener noreferrer"&gt;Skipping a Middleware&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ankitpokhrel/tus-php#setting-up-a-dev-environment-andor-running-examples-locally" rel="noopener noreferrer"&gt;Setting up a dev environment and/or running examples locally&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/tus-php#docker" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/tus-php#contributing" rel="noopener noreferrer"&gt;Contributing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/tus-php#questions-about-this-project" rel="noopener noreferrer"&gt;Questions about this project?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ankitpokhrel/tus-php#supporters" rel="noopener noreferrer"&gt;Supporters&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Installation&lt;/h3&gt;
&lt;/div&gt;
&lt;p&gt;Pull the package via composer.&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;$ composer require ankitpokhrel/tus-php
// Use v1 &lt;span class="pl-k"&gt;for&lt;/span&gt; php7.1, Symfony 3 or 4.&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ankitpokhrel/tus-php" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: Vimeo is now using &lt;a href="https://github.com/ankitpokhrel/tus-php" rel="noopener noreferrer"&gt;TusPHP&lt;/a&gt; in &lt;a href="https://github.com/vimeo/vimeo.php/releases/tag/3.0.0" rel="noopener noreferrer"&gt;v3&lt;/a&gt; of their &lt;a href="https://github.com/vimeo/vimeo.php/pull/186" rel="noopener noreferrer"&gt;Official PHP library for the Vimeo API&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Creating a server to handle our requests
&lt;/h2&gt;

&lt;p&gt;This is how a simple server looks like.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server.php&lt;/span&gt;
&lt;span class="nv"&gt;$server&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\TusPhp\Tus\Server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'redis'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$server&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Exit from current PHP process.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You need to configure your server to respond to a specific endpoint. For example, in Nginx, you would do something like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# nginx.conf

location /files {
    try_files $uri $uri/ /path/to/server.php?$query_string;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Let's assume that the URL to our server is &lt;a href="http://server.tus.local" rel="noopener noreferrer"&gt;http://server.tus.local&lt;/a&gt;. So, based on above nginx configuration we can access our tus endpoints using &lt;a href="http://server.tus.local/files" rel="noopener noreferrer"&gt;http://server.tus.local/files&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now, we have following RESTful endpoints available to work with.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Gather information about server's current configuration
OPTIONS /files

# Check if the given upload is valid
HEAD /files/{upload-key}

# Create a new upload
POST /files

# Resume upload created with POST
PATCH /files/{upload-key}

# Delete the previous upload
DELETE /files/{upload-key}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Check out the &lt;a href="https://tus.io/protocols/resumable-upload.html" rel="noopener noreferrer"&gt;protocol details&lt;/a&gt; for more info about the endpoints.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you are using any frameworks like Laravel, instead of modifying your server config, you can define routes to all tus based endpoints in your framework route file. We will cover this in detail in another tutorial.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Handling upload using tus-php client
&lt;/h2&gt;

&lt;p&gt;Once the server is in place, the client can be used to upload a file in chunks. Let us start by creating a simple HTML form to get input from the user.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"upload.php"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt; &lt;span class="na"&gt;enctype=&lt;/span&gt;&lt;span class="s"&gt;"multipart/form-data"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"tus_file"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"tus-file"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Upload"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;After a form is submitted, we need to follow few steps to handle the upload.&lt;/p&gt;
&lt;h4&gt;
  
  
  1. Create a tus-php client object
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Tus client&lt;/span&gt;
&lt;span class="nv"&gt;$client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\TusPhp\Tus\Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'http://server.tus.local'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The first parameter in the above code is your tus server endpoint.&lt;/p&gt;
&lt;h4&gt;
  
  
  2. Initialize client with file metadata
&lt;/h4&gt;

&lt;p&gt;To keep an upload unique, we need to use some identifier to recognize the upload in upcoming requests. For this, we will have to generate a unique upload key which can be used to resume the upload later. You can either explicitly provide an upload key or let the system generate a key by itself.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Set upload key and file meta&lt;/span&gt;
&lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$uploadKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_FILES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'tus_file'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'tmp_name'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;'your file name'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If you don't provide upload key explicitly, above step will be something like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_FILES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'tus_file'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'tmp_name'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;'your file name'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$uploadKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getKey&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Unique upload key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  3. Upload a chunk
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// $chunkSize is size in bytes, i.e. 5000000 for 5 MB&lt;/span&gt;
&lt;span class="nv"&gt;$bytesUploaded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$chunkSize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Next time, when you want to upload another chunk you can use same upload key to continue.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// To resume upload in next request&lt;/span&gt;
&lt;span class="nv"&gt;$bytesUploaded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$uploadKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$chunkSize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Once the upload is complete, the server verifies upload against the checksum to make sure the uploaded file is not corrupt. The server will use &lt;code&gt;sha256&lt;/code&gt; algorithm by default to verify the upload.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The full implementation of the demo video above can be found &lt;a href="https://github.com/ankitpokhrel/tus-php/tree/master/example/basic" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Handling upload using tus-js-client
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://uppy.io/" rel="noopener noreferrer"&gt;Uppy&lt;/a&gt; is a sleek, modular file uploader plugin developed by same folks behind tus protocol. You can use uppy to seamlessly integrate official &lt;a href="https://github.com/tus/tus-js-client" rel="noopener noreferrer"&gt;tus-js-client&lt;/a&gt; with a tus-php server. That means we are using php implementation of a server and js implementation of a client.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;uppy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Tus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://server.tus.local/files/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// your tus endpoint&lt;/span&gt;
  &lt;span class="na"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;autoRetry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;retryDelays&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;Check out more details in &lt;a href="https://uppy.io/docs/tus/" rel="noopener noreferrer"&gt;uppy docs&lt;/a&gt; and example implementation &lt;a href="https://github.com/ankitpokhrel/tus-php/tree/master/example/uppy" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Partial Uploads
&lt;/h2&gt;

&lt;p&gt;The tus-php Server supports &lt;a href="https://tus.io/protocols/resumable-upload.html#concatenation" rel="noopener noreferrer"&gt;concatenation extension&lt;/a&gt; and is capable of concatenating multiple uploads into a single one enabling clients to perform parallel uploads and to upload non-contiguous chunks.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;The full example of partial uploads can be found &lt;a href="https://github.com/ankitpokhrel/tus-php/tree/master/example/partial" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Words
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/ankitpokhrel/tus-php" rel="noopener noreferrer"&gt;tus-php project&lt;/a&gt; itself is still in its initial stage. Some sections might change in the future. Three different implementation examples can be found in the &lt;a href="https://github.com/ankitpokhrel/tus-php/tree/master/example" rel="noopener noreferrer"&gt;example&lt;/a&gt; folder. Feel free to try and report any issues found. Pull requests and project recommendations are more than welcome.&lt;/p&gt;

&lt;p&gt;Happy Coding!&lt;/p&gt;

</description>
      <category>php</category>
      <category>opensource</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
