Skip to content
Back to skills

2558 Theme Development Dc454c66

ASecurity

Expert guidance for Shopify theme development including file structure, Online Store 2.0 architecture, sections, snippets, and configuration.

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added May 31, 2026
content-marketingjavascriptgojavaapidocumentation

Works with

  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned May 31, 2026

npx -y skills add tools-only/X-Skills --skill 2558-theme-development_dc454c66 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 2558 Theme Development Dc454c66?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 2558 Theme Development Dc454c66
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-2558-theme-development-dc454c66/badge)](https://www.skillsdirectory.com/skills/tools-only-2558-theme-development-dc454c66)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
# Theme Development Reference

Expert guidance for Shopify theme development including file structure, Online Store 2.0 architecture, sections, snippets, and configuration.

## Core Capabilities

### 1. Theme File Structure

Complete directory organisation for Shopify themes:

```
theme/
├── assets/                     {# Static resources #}
│   ├── style.css              {# Main stylesheet #}
│   ├── style.css.liquid       {# Dynamic CSS with Liquid #}
│   ├── theme.js               {# Main JavaScript #}
│   ├── theme.js.liquid        {# Dynamic JS with Liquid #}
│   ├── logo.png               {# Images #}
│   └── fonts/                 {# Custom fonts #}
│
├── config/                     {# Configuration #}
│   ├── settings_schema.json   {# Theme settings UI #}
│   └── settings_data.json     {# Default values #}
│
├── layout/                     {# Master templates #}
│   ├── theme.liquid           {# Main wrapper #}
│   ├── password.liquid        {# Password protection #}
│   └── checkout.liquid        {# Checkout (Plus only) (deprecated - use Checkout Extensibility) #}
│
├── locales/                    {# Translations #}
│   ├── en.default.json        {# English #}
│   └── fr.json                {# French #}
│
├── sections/                   {# Reusable sections #}
│   ├── header.liquid
│   ├── hero-banner.liquid
│   ├── product-card.liquid
│   └── footer.liquid
│
├── snippets/                   {# Reusable partials #}
│   ├── product-price.liquid
│   ├── product-rating.liquid
│   └── icon.liquid
│
└── templates/                  {# Page templates #}
    ├── index.json              {# Homepage (JSON) #}
    ├── product.json            {# Product page (JSON) #}
    ├── collection.json         {# Collection page (JSON) #}
    ├── product.liquid          {# Product (Liquid - legacy) #}
    ├── cart.liquid
    ├── search.liquid
    ├── page.liquid
    ├── 404.liquid
    └── customers/
        ├── account.liquid
        ├── login.liquid
        └── register.liquid
```

**Horizon theme:** Shopify's newest reference theme (2025) - uses container queries, View Transitions API, and CSS custom properties. Replaces Dawn as the recommended starting point for new themes.

### 2. JSON Templates (Online Store 2.0)

Modern template format using JSON configuration:

**templates/index.json (Homepage):**
```json
{
  "sections": {
    "hero": {
      "type": "hero-banner",
      "settings": {
        "heading": "Summer Collection",
        "subheading": "New arrivals",
        "button_text": "Shop Now",
        "button_link": "/collections/all"
      }
    },
    "featured": {
      "type": "featured-products",
      "blocks": {
        "block_1": {
          "type": "product",
          "settings": {
            "product": "snowboard"
          }
        },
        "block_2": {
          "type": "product",
          "settings": {
            "product": "skateboard"
          }
        }
      },
      "block_order": ["block_1", "block_2"],
      "settings": {
        "title": "Featured Products",
        "products_to_show": 4
      }
    }
  },
  "order": ["hero", "featured"]
}
```

**templates/product.json:**
```json
{
  "sections": {
    "main": {
      "type": "main-product",
      "settings": {
        "show_vendor": true,
        "show_quantity": true,
        "enable_zoom": true
      }
    },
    "recommendations": {
      "type": "product-recommendations",
      "settings": {
        "heading": "You may also like",
        "products_to_show": 4
      }
    }
  },
  "order": ["main", "recommendations"]
}
```

### 3. Section Architecture

Sections are reusable content blocks with schema configuration:

**sections/hero-banner.liquid:**
```liquid
<div class="hero" style="background-color: {{ section.settings.background_color }}">
  {% if section.settings.image %}
    <img
      src="{{ section.settings.image | img_url: '1920x' }}"
      alt="{{ section.settings.heading }}"
      loading="lazy"
    >
  {% endif %}

  <div class="hero__content">
    {% if section.settings.heading != blank %}
      <h1>{{ section.settings.heading }}</h1>
    {% endif %}

    {% if section.settings.subheading != blank %}
      <p>{{ section.settings.subheading }}</p>
    {% endif %}

    {% if section.settings.button_text != blank %}
      <a href="{{ section.settings.button_link }}" class="button">
        {{ section.settings.button_text }}
      </a>
    {% endif %}
  </div>
</div>

{% stylesheet %}
  .hero {
    position: relative;
    min-height: 500px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--spacing, 2rem);
  }

  .hero img {
    position: absolute;
    width: 100%;
    height: 100%;
    object-fit: cover;
    z-index: -1;
  }

  .hero__content {
    text-align: center;
    max-width: 600px;
  }
{% endstylesheet %}

{% javascript %}
  console.log('Hero banner loaded');
{% endjavascript %}

{% schema %}
{
  "name": "Hero Banner",
  "tag": "section",
  "class": "hero-section",
  "settings": [
    {
      "type": "text",
      "id": "heading",
      "label": "Heading",
      "default": "Welcome"
    },
    {
      "type": "textarea",
      "id": "subheading",
      "label": "Subheading",
      "default": "Discover our collection"
    },
    {
      "type": "image_picker",
      "id": "image",
      "label": "Background Image"
    },
    {
      "type": "color",
      "id": "background_color",
      "label": "Background Colour",
      "default": "#000000"
    },
    {
      "type": "text",
      "id": "button_text",
      "label": "Button Text",
      "default": "Shop Now"
    },
    {
      "type": "url",
      "id": "button_link",
      "label": "Button Link"
    }
  ],
  "presets": [
    {
      "name": "Hero Banner"
    }
  ]
}
{% endschema %}
```

### 4. Sections with Blocks

Sections can contain dynamic blocks for flexible layouts:

**sections/featured-products.liquid:**
```liquid
<div class="featured-products" {{ section.shopify_attributes }}>
  <h2>{{ section.settings.title }}</h2>

  <div class="product-grid">
    {% for block in section.blocks %}
      <div class="product-item" {{ block.shopify_attributes }}>
        {% case block.type %}
          {% when 'product' %}
            {% assign product = all_products[block.settings.product] %}
            {% render 'product-card', product: product %}

          {% when 'collection' %}
            {% assign collection = collections[block.settings.collection] %}
            <h3>{{ collection.title }}</h3>
            {% for product in collection.products limit: block.settings.products_to_show %}
              {% render 'product-card', product: product %}
            {% endfor %}

          {% when 'heading' %}
            <h3>{{ block.settings.heading }}</h3>

          {% when 'text' %}
            <div class="text-block">
              {{ block.settings.text }}
            </div>
        {% endcase %}
      </div>
    {% endfor %}
  </div>
</div>

{% schema %}
{
  "name": "Featured Products",
  "tag": "section",
  "settings": [
    {
      "type": "text",
      "id": "title",
      "label": "Section Title",
      "default": "Featured Products"
    },
    {
      "type": "range",
      "id": "products_per_row",
      "label": "Products per Row",
      "min": 2,
      "max": 5,
      "step": 1,
      "default": 4
    }
  ],
  "blocks": [
    {
      "type": "product",
      "name": "Product",
      "settings": [
        {
          "type": "product",
          "id": "product",
          "label": "Product"
        }
      ]
    },
    {
      "type": "collection",
      "name": "Collection",
      "settings": [
        {
          "type": "collection",
          "id": "collection",
          "label": "Collection"
        },
        {
          "type": "range",
          "id": "products_to_show",
          "label": "Products to Show",
          "min": 1,
          "max": 12,
          "step": 1,
          "default": 4
        }
      ]
    },
    {
      "type": "heading",
      "name": "Heading",
      "settings": [
        {
          "type": "text",
          "id": "heading",
          "label": "Heading Text"
        }
      ]
    },
    {
      "type": "text",
      "name": "Text Block",
      "settings": [
        {
          "type": "richtext",
          "id": "text",
          "label": "Text Content"
        }
      ]
    }
  ],
  "presets": [
    {
      "name": "Featured Products",
      "blocks": [
        {
          "type": "product"
        },
        {
          "type": "product"
        },
        {
          "type": "product"
        }
      ]
    }
  ],
  "max_blocks": 12
}
{% endschema %}
```

### 5. Snippets

Reusable template partials:

**snippets/product-card.liquid:**
```liquid
{% comment %}
  Usage: {% render 'product-card', product: product, show_vendor: true %}
{% endcomment %}

<div class="product-card">
  <a href="{{ product.url }}">
    {% if product.featured_image %}
      <img
        src="{{ product.featured_image | img_url: '400x400' }}"
        alt="{{ product.featured_image.alt | escape }}"
        loading="lazy"
      >
    {% else %}
      {{ 'product-1' | placeholder_svg_tag: 'placeholder' }}
    {% endif %}
  </a>

  <div class="product-card__info">
    {% if show_vendor and product.vendor != blank %}
      <p class="product-card__vendor">{{ product.vendor }}</p>
    {% endif %}

    <h3 class="product-card__title">
      <a href="{{ product.url }}">{{ product.title }}</a>
    </h3>

    <div class="product-card__price">
      {% render 'product-price', product: product %}
    </div>

    {% unless product.available %}
      <p class="sold-out">Sold Out</p>
    {% endunless %}
  </div>
</div>
```

**snippets/product-price.liquid:**
```liquid
{% comment %}
  Usage: {% render 'product-price', product: product %}
{% endcomment %}

{% if product.compare_at_price > product.price %}
  <span class="price price--sale">
    {{ product.price | money }}
  </span>
  <span class="price price--compare">
    {{ product.compare_at_price | money }}
  </span>
  <span class="price__badge">
    Save {{ product.compare_at_price | minus: product.price | money }}
  </span>
{% else %}
  <span class="price">
    {{ product.price | money }}
  </span>
{% endif %}

{% if product.price_varies %}
  <span class="price__from">from</span>
{% endif %}
```

### 6. Settings Schema

Complete theme customisation interface:

**config/settings_schema.json:**
```json
[
  {
    "name": "theme_info",
    "theme_name": "My Theme",
    "theme_version": "1.0.0",
    "theme_author": "Your Name",
    "theme_documentation_url": "https://...",
    "theme_support_url": "https://..."
  },
  {
    "name": "Colors",
    "settings": [
      {
        "type": "header",
        "content": "Colour Scheme"
      },
      {
        "type": "color",
        "id": "color_primary",
        "label": "Primary Colour",
        "default": "#000000"
      },
      {
        "type": "color",
        "id": "color_secondary",
        "label": "Secondary Colour",
        "default": "#ffffff"
      },
      {
        "type": "color_background",
        "id": "color_body_bg",
        "label": "Body Background"
      }
    ]
  },
  {
    "name": "Typography",
    "settings": [
      {
        "type": "font_picker",
        "id": "type_header_font",
        "label": "Heading Font",
        "default": "helvetica_n7"
      },
      {
        "type": "font_picker",
        "id": "type_body_font",
        "label": "Body Font",
        "default": "helvetica_n4"
      },
      {
        "type": "range",
        "id": "type_base_size",
        "label": "Base Font Size",
        "min": 12,
        "max": 24,
        "step": 1,
        "default": 16,
        "unit": "px"
      }
    ]
  },
  {
    "name": "Layout",
    "settings": [
      {
        "type": "select",
        "id": "layout_style",
        "label": "Layout Style",
        "options": [
          { "value": "boxed", "label": "Boxed" },
          { "value": "full-width", "label": "Full Width" },
          { "value": "wide", "label": "Wide" }
        ],
        "default": "full-width"
      },
      {
        "type": "checkbox",
        "id": "layout_sidebar_enabled",
        "label": "Enable Sidebar",
        "default": true
      }
    ]
  },
  {
    "name": "Header",
    "settings": [
      {
        "type": "image_picker",
        "id": "logo",
        "label": "Logo"
      },
      {
        "type": "range",
        "id": "logo_max_width",
        "label": "Logo Width",
        "min": 50,
        "max": 300,
        "step": 10,
        "default": 150,
        "unit": "px"
      },
      {
        "type": "link_list",
        "id": "main_menu",
        "label": "Main Menu"
      },
      {
        "type": "checkbox",
        "id": "header_sticky",
        "label": "Sticky Header",
        "default": false
      }
    ]
  },
  {
    "name": "Social Media",
    "settings": [
      {
        "type": "header",
        "content": "Social Accounts"
      },
      {
        "type": "url",
        "id": "social_twitter",
        "label": "Twitter URL",
        "info": "https://twitter.com/username"
      },
      {
        "type": "url",
        "id": "social_facebook",
        "label": "Facebook URL"
      },
      {
        "type": "url",
        "id": "social_instagram",
        "label": "Instagram URL"
      }
    ]
  }
]
```

### 7. Layout Files

Master template wrappers:

**layout/theme.liquid:**
```liquid
<!doctype html>
<html lang="{{ request.locale.iso_code }}">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">

  <title>
    {{ page_title }}
    {%- if current_tags %} &ndash; {{ 'general.meta.tags' | t: tags: current_tags.join(', ') }}{% endif -%}
    {%- if current_page != 1 %} &ndash; {{ 'general.meta.page' | t: page: current_page }}{% endif -%}
    {%- unless page_title contains shop.name %} &ndash; {{ shop.name }}{% endunless -%}
  </title>

  {{ content_for_header }}

  <link rel="stylesheet" href="{{ 'style.css' | asset_url }}">
  <script src="{{ 'theme.js' | asset_url }}" defer></script>
</head>
<body class="template-{{ request.page_type }}">
  {% section 'header' %}

  <main role="main">
    {{ content_for_layout }}
  </main>

  {% section 'footer' %}
</body>
</html>
```

## Settings Schema Input Types

All 28+ input types for theme customisation in `settings_schema.json` and section schemas.

### Text Inputs

#### text

Single-line text input:

```json
{
  "type": "text",
  "id": "store_name",
  "label": "Store Name",
  "default": "My Store",
  "placeholder": "Enter store name",
  "info": "This appears in the header"
}
```

#### textarea

Multi-line text input:

```json
{
  "type": "textarea",
  "id": "footer_text",
  "label": "Footer Text",
  "default": "© 2025 My Store. All rights reserved.",
  "placeholder": "Enter footer text"
}
```

#### html

HTML code editor:

```json
{
  "type": "html",
  "id": "custom_html",
  "label": "Custom HTML",
  "default": "<p>Welcome to our store!</p>",
  "info": "Add custom HTML code"
}
```

#### richtext

WYSIWYG rich text editor:

```json
{
  "type": "richtext",
  "id": "announcement_content",
  "label": "Announcement Bar Content",
  "default": "<p>Free shipping on orders over $50!</p>"
}
```

### Numeric Inputs

#### number

Numeric input field:

```json
{
  "type": "number",
  "id": "products_per_page",
  "label": "Products Per Page",
  "default": 12,
  "min": 1,
  "max": 100,
  "step": 1,
  "info": "Number of products to show per page"
}
```

#### range

Slider input:

```json
{
  "type": "range",
  "id": "columns",
  "label": "Number of Columns",
  "min": 2,
  "max": 6,
  "step": 1,
  "default": 4,
  "unit": "columns",
  "info": "Adjust the grid layout"
}
```

**Common units:**
- `px` - Pixels
- `%` - Percentage
- `em` - Em units
- `rem` - Root em units
- Custom text (like "columns", "items")

### Boolean Inputs

#### checkbox

Toggle checkbox:

```json
{
  "type": "checkbox",
  "id": "show_search",
  "label": "Show Search Bar",
  "default": true,
  "info": "Display search in header"
}
```

#### boolean

Boolean setting (same as checkbox):

```json
{
  "type": "boolean",
  "id": "enable_feature",
  "label": "Enable Feature",
  "default": false
}
```

### Selection Inputs

#### select

Dropdown menu:

```json
{
  "type": "select",
  "id": "layout_style",
  "label": "Layout Style",
  "options": [
    {
      "value": "boxed",
      "label": "Boxed"
    },
    {
      "value": "full-width",
      "label": "Full Width"
    },
    {
      "value": "wide",
      "label": "Wide"
    }
  ],
  "default": "full-width",
  "info": "Choose your layout style"
}
```

#### radio

Radio button selection:

```json
{
  "type": "radio",
  "id": "text_alignment",
  "label": "Text Alignment",
  "options": [
    { "value": "left", "label": "Left" },
    { "value": "center", "label": "Centre" },
    { "value": "right", "label": "Right" }
  ],
  "default": "center"
}
```

### Colour Inputs

#### color

Colour picker:

```json
{
  "type": "color",
  "id": "primary_color",
  "label": "Primary Colour",
  "default": "#000000",
  "info": "Main brand colour"
}
```

#### color_background

Colour with gradient support:

```json
{
  "type": "color_background",
  "id": "section_background",
  "label": "Section Background",
  "default": "linear-gradient(#ffffff, #000000)"
}
```

**Supports:**
- Solid colours: `#ffffff`
- Linear gradients: `linear-gradient(#fff, #000)`
- Radial gradients
- With opacity

### Media Inputs

#### image_picker

Image upload and selection:

```json
{
  "type": "image_picker",
  "id": "logo",
  "label": "Logo Image",
  "info": "Recommended size: 300x100px"
}
```

Access in Liquid:

```liquid
{% if settings.logo %}
  <img src="{{ settings.logo | img_url: '300x' }}" alt="{{ shop.name }}">
{% endif %}

{{ settings.logo.width }}
{{ settings.logo.height }}
{{ settings.logo.alt }}
{{ settings.logo.src }}
```

#### media

Image or video picker:

```json
{
  "type": "media",
  "id": "hero_media",
  "label": "Hero Media",
  "accept": ["image", "video"],
  "info": "Upload image or video"
}
```

#### video_url

Video URL input (YouTube, Vimeo):

```json
{
  "type": "video_url",
  "id": "promo_video",
  "label": "Promo Video",
  "accept": ["youtube", "vimeo"],
  "placeholder": "https://www.youtube.com/watch?v=...",
  "info": "YouTube or Vimeo URL"
}
```

Access in Liquid:

```liquid
{% if settings.promo_video %}
  {{ settings.promo_video.type }}  {# youtube or vimeo #}
  {{ settings.promo_video.id }}    {# Video ID #}
{% endif %}
```

### Typography Inputs

#### font_picker

Google Fonts selector:

```json
{
  "type": "font_picker",
  "id": "heading_font",
  "label": "Heading Font",
  "default": "helvetica_n7",
  "info": "Font for headings"
}
```

**Font format:** `family_weight`
- `n4` - Normal 400
- `n7` - Bold 700
- `i4` - Italic 400

Access in Liquid:

```liquid
{{ settings.heading_font.family }}
{{ settings.heading_font.weight }}
{{ settings.heading_font.style }}

{# CSS font face #}
<style>
  {{ settings.heading_font | font_face }}

  h1, h2, h3 {
    font-family: {{ settings.heading_font.family }}, {{ settings.heading_font.fallback_families }};
    font-weight: {{ settings.heading_font.weight }};
    font-style: {{ settings.heading_font.style }};
  }
</style>
```

### Resource Pickers

#### product

Product selector:

```json
{
  "type": "product",
  "id": "featured_product",
  "label": "Featured Product",
  "info": "Select a product to feature"
}
```

Access in Liquid:

```liquid
{% assign product = all_products[settings.featured_product] %}
{{ product.title }}
{{ product.price | money }}
```

#### collection

Collection selector:

```json
{
  "type": "collection",
  "id": "featured_collection",
  "label": "Featured Collection",
  "info": "Select a collection to feature"
}
```

Access in Liquid:

```liquid
{% assign collection = collections[settings.featured_collection] %}
{{ collection.title }}
{% for product in collection.products limit: 4 %}
  {{ product.title }}
{% endfor %}
```

#### page

Page selector:

```json
{
  "type": "page",
  "id": "about_page",
  "label": "About Page",
  "info": "Link to about page"
}
```

Access in Liquid:

```liquid
{% assign page = pages[settings.about_page] %}
<a href="{{ page.url }}">{{ page.title }}</a>
{{ page.content }}
```

#### blog

Blog selector:

```json
{
  "type": "blog",
  "id": "main_blog",
  "label": "Main Blog",
  "info": "Select your primary blog"
}
```

Access in Liquid:

```liquid
{% assign blog = blogs[settings.main_blog] %}
{{ blog.title }}
{% for article in blog.articles limit: 3 %}
  {{ article.title }}
{% endfor %}
```

#### article

Article (blog post) selector:

```json
{
  "type": "article",
  "id": "featured_article",
  "label": "Featured Article",
  "info": "Select an article to feature"
}
```

#### link_list

Menu/navigation selector:

```json
{
  "type": "link_list",
  "id": "main_menu",
  "label": "Main Navigation",
  "default": "main-menu",
  "info": "Select menu for header"
}
```

Access in Liquid:

```liquid
{% assign menu = linklists[settings.main_menu] %}
{% for link in menu.links %}
  <a href="{{ link.url }}">{{ link.title }}</a>

  {% if link.links.size > 0 %}
    {# Nested links #}
    {% for child_link in link.links %}
      <a href="{{ child_link.url }}">{{ child_link.title }}</a>
    {% endfor %}
  {% endif %}
{% endfor %}
```

### URL Inputs

#### url

URL input field:

```json
{
  "type": "url",
  "id": "twitter_url",
  "label": "Twitter URL",
  "placeholder": "https://twitter.com/username",
  "info": "Your Twitter profile URL"
}
```

### Date & Time Inputs

#### date

Date picker:

```json
{
  "type": "date",
  "id": "sale_end_date",
  "label": "Sale End Date",
  "info": "When the sale ends"
}
```

Access in Liquid:

```liquid
{{ settings.sale_end_date | date: '%B %d, %Y' }}
```

### Organisation Elements

#### header

Visual separator with heading:

```json
{
  "type": "header",
  "content": "Colour Scheme Settings",
  "info": "Configure your colour palette"
}
```

Not a setting, just a visual divider in the settings panel.

#### paragraph

Informational text block:

```json
{
  "type": "paragraph",
  "content": "These settings control the appearance of your product cards. Make sure to preview changes on different screen sizes."
}
```

### Advanced Inputs

#### liquid

Liquid code editor:

```json
{
  "type": "liquid",
  "id": "custom_liquid",
  "label": "Custom Liquid Code",
  "info": "Add custom Liquid code"
}
```

#### inline_richtext

Inline rich text (no `<p>` wrapper):

```json
{
  "type": "inline_richtext",
  "id": "banner_text",
  "label": "Banner Text",
  "default": "Welcome to <strong>our store</strong>!",
  "info": "Text without paragraph wrapper"
}
```

## Best Practices

1. **Use JSON templates** for Online Store 2.0 compatibility
2. **Make sections dynamic** with blocks for merchant flexibility
3. **Add `shopify_attributes`** to section/block containers for theme editor
4. **Provide sensible defaults** in schema settings
5. **Use snippets** for repeated UI components
6. **Add `{% stylesheet %}` and `{% javascript %}`** blocks in sections for scoped styles
7. **Include accessibility** attributes (ARIA labels, alt text)
8. **Test in theme editor** to ensure live preview works
9. **Document snippet parameters** with comments
10. **Use semantic HTML** for better SEO
11. **Group related settings** into logical sections
12. **Provide clear labels and info text** for guidance
13. **Use appropriate input types** for each setting
14. **Add placeholder text** for URL and text inputs
15. **Use headers and paragraphs** to organise complex sections
16. **Limit range values** to reasonable min/max
17. **Test in theme customiser** to ensure good UX
18. **Document dependencies** between settings
19. **Consider mobile experience** when choosing input types

Files in this skill

  • README.md1.1 KB
  • skill.md23.6 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.