<?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: Theodore Kelechukwu Onyejiaku</title>
    <description>The latest articles on DEV Community by Theodore Kelechukwu Onyejiaku (@javascriptar).</description>
    <link>https://dev.to/javascriptar</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F200542%2Fe402ac5c-f2b1-413d-8bc2-9f8bc5bf24d8.jpg</url>
      <title>DEV Community: Theodore Kelechukwu Onyejiaku</title>
      <link>https://dev.to/javascriptar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/javascriptar"/>
    <language>en</language>
    <item>
      <title>10 Tips for Writing Technical Blog Posts Developers Actually Read</title>
      <dc:creator>Theodore Kelechukwu Onyejiaku</dc:creator>
      <pubDate>Tue, 27 Oct 2026 17:26:26 +0000</pubDate>
      <link>https://dev.to/javascriptar/10-tips-for-writing-technical-blog-posts-developers-actually-read-1g4</link>
      <guid>https://dev.to/javascriptar/10-tips-for-writing-technical-blog-posts-developers-actually-read-1g4</guid>
      <description>&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1182tc2moe04cd8uildh.jpg" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1182tc2moe04cd8uildh.jpg" alt="Notebook and pen next to a keyboard" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  10 Tips for Writing Technical Blog Posts Developers Actually Read
&lt;/h1&gt;

&lt;p&gt;Developers skim. Your job is to make skimming rewarding.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Lead with the outcome.&lt;/strong&gt; Tell readers what they'll build in the first paragraph.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Show the finished code early.&lt;/strong&gt; People want to know if it's worth their time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One idea per section.&lt;/strong&gt; Headings should read like a table of contents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep code blocks runnable.&lt;/strong&gt; Copy-paste should just work.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Explain the why, not just the how.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use real names.&lt;/strong&gt; &lt;code&gt;fetchPosts()&lt;/code&gt; beats &lt;code&gt;foo()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Add screenshots for UI steps.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State your versions.&lt;/strong&gt; "Strapi 5, Node 20" saves hours of confusion.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Link to the repo.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;End with next steps.&lt;/strong&gt; Give readers somewhere to go.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Bonus
&lt;/h2&gt;

&lt;p&gt;Read your post out loud before publishing. If you stumble, your readers will too.&lt;/p&gt;

</description>
      <category>writing</category>
      <category>blogging</category>
      <category>content</category>
    </item>
    <item>
      <title>Why Every Developer Should Own Their Content</title>
      <dc:creator>Theodore Kelechukwu Onyejiaku</dc:creator>
      <pubDate>Tue, 27 Oct 2026 17:16:09 +0000</pubDate>
      <link>https://dev.to/javascriptar/why-every-developer-should-own-their-content-38eh</link>
      <guid>https://dev.to/javascriptar/why-every-developer-should-own-their-content-38eh</guid>
      <description>&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7kll5wkzqqmzx9pe1h1i.jpg" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7kll5wkzqqmzx9pe1h1i.jpg" alt="Person writing in a journal outdoors" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Why Every Developer Should Own Their Content
&lt;/h1&gt;

&lt;p&gt;Platforms come and go. Algorithms change. Paywalls appear. If your writing only lives on someone else's site, you're renting your audience.&lt;/p&gt;

&lt;h2&gt;
  
  
  The POSSE approach
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Publish (on your) Own Site, Syndicate Elsewhere.&lt;/strong&gt; Your site holds the original; Medium, Dev.to, and social networks get copies that link back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Longevity.&lt;/strong&gt; Your posts survive platform shutdowns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO.&lt;/strong&gt; Links and authority accumulate on your domain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control.&lt;/strong&gt; You decide the design, the ads (or lack of them), and the license.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;You don't need anything fancy. A headless CMS plus a static site generator is plenty. Add a publishing plugin, and syndication becomes a button click.&lt;/p&gt;

</description>
      <category>content</category>
      <category>blogging</category>
      <category>writing</category>
    </item>
    <item>
      <title>Securing API Keys in a Strapi Plugin</title>
      <dc:creator>Theodore Kelechukwu Onyejiaku</dc:creator>
      <pubDate>Mon, 26 Oct 2026 07:05:13 +0000</pubDate>
      <link>https://dev.to/javascriptar/securing-api-keys-in-a-strapi-plugin-56cg</link>
      <guid>https://dev.to/javascriptar/securing-api-keys-in-a-strapi-plugin-56cg</guid>
      <description>&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuypovcrhhr5y4cqqgvfz.jpg" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuypovcrhhr5y4cqqgvfz.jpg" alt="Padlock on a keyboard" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Securing API Keys in a Strapi Plugin
&lt;/h1&gt;

&lt;p&gt;A publishing plugin needs tokens for Medium, Dev.to, and Bluesky. Here's how to keep them safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Never hard-code secrets
&lt;/h2&gt;

&lt;p&gt;Keep them out of your source code and out of git.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: Environment variables
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DEVTO_API_KEY=xxxx
MEDIUM_TOKEN=xxxx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config/plugins.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-publisher&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;devtoApiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DEVTO_API_KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&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;h2&gt;
  
  
  Option 2: A settings page
&lt;/h2&gt;

&lt;p&gt;If admins should manage keys from the UI, store them with the plugin store and protect the route with admin permissions. Never return the raw key to the browser — send back a masked value like &lt;code&gt;••••1234&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;code&gt;.env&lt;/code&gt; is in &lt;code&gt;.gitignore&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Settings routes require an admin permission&lt;/li&gt;
&lt;li&gt;[ ] Keys are masked in API responses&lt;/li&gt;
&lt;li&gt;[ ] Tokens are rotated if leaked&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>strapi</category>
      <category>plugin</category>
      <category>security</category>
    </item>
    <item>
      <title>Posting to Bluesky Programmatically with the AT Protocol</title>
      <dc:creator>Theodore Kelechukwu Onyejiaku</dc:creator>
      <pubDate>Mon, 26 Oct 2026 07:02:45 +0000</pubDate>
      <link>https://dev.to/javascriptar/posting-to-bluesky-programmatically-with-the-at-protocol-2jj1</link>
      <guid>https://dev.to/javascriptar/posting-to-bluesky-programmatically-with-the-at-protocol-2jj1</guid>
      <description>&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fslovfqwvl77q1ouuwqb7.jpg" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fslovfqwvl77q1ouuwqb7.jpg" alt="Blue sky with clouds" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Posting to Bluesky Programmatically with the AT Protocol
&lt;/h1&gt;

&lt;p&gt;Bluesky is built on the AT Protocol, and the official &lt;code&gt;@atproto/api&lt;/code&gt; package makes posting straightforward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @atproto/api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Log in and post
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AtpAgent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;RichText&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@atproto/api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&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;AtpAgent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;service&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://bsky.social&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;you.bsky.social&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BSKY_APP_PASSWORD&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rt&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;RichText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New post: https://mystite.com/my-article&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detectFacets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;facets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;facets&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tips
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use an &lt;strong&gt;app password&lt;/strong&gt;, not your main password.&lt;/li&gt;
&lt;li&gt;Posts are limited to 300 characters — share a teaser plus a link.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;detectFacets&lt;/code&gt; turns URLs and mentions into clickable links.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>bluesky</category>
      <category>javascript</category>
      <category>api</category>
    </item>
    <item>
      <title>Tracking Publishing Status Across Multiple Platforms</title>
      <dc:creator>Theodore Kelechukwu Onyejiaku</dc:creator>
      <pubDate>Fri, 23 Oct 2026 16:48:25 +0000</pubDate>
      <link>https://dev.to/javascriptar/tracking-publishing-status-across-multiple-platforms-17l</link>
      <guid>https://dev.to/javascriptar/tracking-publishing-status-across-multiple-platforms-17l</guid>
      <description>&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvle8y063x0xnj3jvgcv7.jpg" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvle8y063x0xnj3jvgcv7.jpg" alt="Dashboard with charts on a monitor" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Tracking Publishing Status Across Multiple Platforms
&lt;/h1&gt;

&lt;p&gt;Once you publish to several platforms, a new question appears: &lt;em&gt;where has this post actually gone?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Model the state
&lt;/h2&gt;

&lt;p&gt;Give each post a record per platform:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"blog"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sr3qxtvl2q275rq59udaihac"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"medium"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"published"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://medium.com/..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"devto"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pending"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bluesky"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"failed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Invalid token"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Show it in the admin
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A badge per platform (green = published, grey = not yet, red = failed).&lt;/li&gt;
&lt;li&gt;A filter so editors can see &lt;em&gt;only posts not yet on Medium&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;A retry button for failed attempts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Store the returned URLs
&lt;/h2&gt;

&lt;p&gt;Every platform returns a URL after publishing. Save it — it's how you link back, update later, or detect duplicates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Visible status turns a black-box button into a workflow your editors can trust.&lt;/p&gt;

</description>
      <category>strapi</category>
      <category>plugin</category>
      <category>content</category>
    </item>
    <item>
      <title>What Are Agent Skills and How To Use Them</title>
      <dc:creator>Theodore Kelechukwu Onyejiaku</dc:creator>
      <pubDate>Fri, 13 Mar 2026 09:59:47 +0000</pubDate>
      <link>https://dev.to/strapi/what-are-agent-skills-and-how-to-use-them-3jno</link>
      <guid>https://dev.to/strapi/what-are-agent-skills-and-how-to-use-them-3jno</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agent Skills are an open standard for packaging reusable workflows, domain expertise, and new capabilities into portable folders that any AI agent can discover and use — think of them as "saved prompts" on steroids&lt;/li&gt;
&lt;li&gt;Skills use a three-tier progressive disclosure model to protect the context window: only the name and description load at startup (~30-50 tokens per skill), the full SKILL.md loads when triggered, and reference files load only when needed during execution&lt;/li&gt;
&lt;li&gt;Originally created by Anthropic, skills are now an open standard at agentskills.io, adopted by 26+ platforms including Claude, OpenAI Codex, Gemini CLI, GitHub Copilot, Cursor, and VS Code&lt;/li&gt;
&lt;li&gt;A skill is just a folder with a &lt;code&gt;SKILL.md&lt;/code&gt; file (YAML frontmatter + markdown instructions) plus optional &lt;code&gt;scripts/&lt;/code&gt;, &lt;code&gt;references/&lt;/code&gt;, and &lt;code&gt;assets/&lt;/code&gt; directories — you can build one in minutes&lt;/li&gt;
&lt;li&gt;Skills are composable: combine custom skills with built-in document skills and MCP servers to build complex, predictable workflows that produce consistent output every time&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Are Agent Skills?
&lt;/h2&gt;

&lt;p&gt;If you find yourself typing the same prompt across conversations — a weekly report format, a code review checklist, a data analysis pipeline — you've already felt the problem that skills solve. Agent Skills are folders of instructions that package repeated workflows, specialized knowledge, or new capabilities for your AI agent. Instead of copying and pasting lengthy prompts every session, you write them once as a skill and reuse them across every conversation.&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%2Fgxtp3f750ziy5xf6vxzl.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%2Fgxtp3f750ziy5xf6vxzl.png" alt="skills-overview.png" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But skills go far beyond "saved prompts." They're an &lt;strong&gt;open standard&lt;/strong&gt; originally created by Anthropic and now governed as a cross-platform specification at &lt;a href="https://agentskills.io" rel="noopener noreferrer"&gt;agentskills.io&lt;/a&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%2Fhtxfydvfai67htzs2vck.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%2Fhtxfydvfai67htzs2vck.png" alt="what-ara-agent-skills.png" width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On December 18, 2025, Anthropic released Agent Skills as an open standard, publishing the specification and SDK for any AI platform to adopt. This means a skill you build for Claude works identically in OpenAI Codex, Gemini CLI, GitHub Copilot, Cursor, VS Code, and over 20 other platforms that have adopted the standard.&lt;/p&gt;

&lt;p&gt;The simplest way to think about it: &lt;strong&gt;MCP gives your agent access to external tools and data. Skills teach your agent &lt;em&gt;what to do&lt;/em&gt; with those tools and data.&lt;/strong&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%2F0ij1zxrg82xaiv0brx9p.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%2F0ij1zxrg82xaiv0brx9p.png" alt="skill-overveiw.png" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Filesystem&lt;/strong&gt; (left) — Skills that provide instructions and domain knowledge to the agent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent&lt;/strong&gt; (center) — Contains the LLM and Tools working together&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP Servers&lt;/strong&gt; (right) — External tools and data connections&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subagents&lt;/strong&gt; (bottom) — Spawned by the main agent for parallel/specialized work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Skills flow &lt;em&gt;into&lt;/em&gt; the agent as knowledge, MCP connects &lt;em&gt;bidirectionally&lt;/em&gt; for external capabilities, and Subagents are dispatched &lt;em&gt;from&lt;/em&gt; the agent. This visually reinforces the point: &lt;strong&gt;MCP gives your agent access to external tools and data. Skills teach your agent &lt;em&gt;what to do&lt;/em&gt; with those tools and data.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Skills Matter: From Prompts to Packages
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Prompt Repetition Problem
&lt;/h3&gt;

&lt;p&gt;Consider a content creator who publishes blog posts every week. Without skills, every conversation starts the same way:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Paste the blog post structure rules (TL;DR section, heading hierarchy, citations format)&lt;/li&gt;
&lt;li&gt;Paste the SEO guidelines (keyword density, meta description format, internal linking rules)&lt;/li&gt;
&lt;li&gt;Paste the brand voice instructions (tone, audience, formatting preferences)&lt;/li&gt;
&lt;li&gt;Paste or describe the topic and source material&lt;/li&gt;
&lt;li&gt;Hope the output actually follows all those rules consistently&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now multiply that across a team of five writers, each with their own slightly different version of those instructions. The output quality varies from session to session and person to person.&lt;/p&gt;

&lt;p&gt;This approach has three fundamental issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Context window pollution&lt;/strong&gt; — Every piece of instruction you paste consumes tokens that could be used for actual content. The more instructions you front-load, the faster your context fills up and the higher the likelihood of degradation in response quality&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No portability&lt;/strong&gt; — Your workflow lives in your clipboard or a shared doc somewhere, not in a versioned, shareable artifact that can be installed and used consistently&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No consistency&lt;/strong&gt; — Without a standardized format, different people will interpret and paste those instructions differently, and you can never guarantee the agent follows every rule every time&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How Skills Fix This
&lt;/h3&gt;

&lt;p&gt;Skills address all three problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Progressive disclosure&lt;/strong&gt; — Only the skill's name and description are loaded at startup (roughly 30-50 tokens per skill). The full instructions load only when the skill is triggered. Referenced files, scripts, and assets load only when needed during execution. You can have hundreds of skills installed without any impact on performance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portability&lt;/strong&gt; — A skill is a folder. Share it as a zip file, push it to a Git repository, or publish it to a marketplace. Since it's an open standard, it works across Claude, Codex, Gemini CLI, and every other platform that supports the spec&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repeatability&lt;/strong&gt; — The same instructions execute the same way every time, for every team member, across every platform. In a non-deterministic system where you can never fully predict the model's output, skills provide the structure to make workflows as predictable as possible&lt;/li&gt;
&lt;/ul&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%2F8b7gtoz1je62yzq1ngg5.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%2F8b7gtoz1je62yzq1ngg5.png" alt="Screenshot 2026-02-06 at 1.09.49 PM.png" width="800" height="1210"&gt;&lt;/a&gt;&lt;br&gt;
Anthropic frames the context window as a &lt;strong&gt;public good&lt;/strong&gt; — the more data you add, the more tokens you consume, the faster it fills, and the greater the risk of context degradation. Progressive disclosure is what makes skills fundamentally different from just pasting a big prompt. Skills are intentional about what information goes into the context window and what stays on disk until it's actually needed.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Anatomy of a Skill
&lt;/h2&gt;

&lt;p&gt;Every skill follows the same structure, defined by the &lt;a href="https://agentskills.io/specification" rel="noopener noreferrer"&gt;Agent Skills Specification&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;my-skill/
├── SKILL.md              # Required — YAML frontmatter + markdown instructions
├── scripts/              # Optional — executable code (Python, Bash, JS)
├── references/           # Optional — additional documentation
└── assets/               # Optional — templates, images, logos, data files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The SKILL.md File
&lt;/h3&gt;

&lt;p&gt;This is the only required file. It has two parts:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. YAML Frontmatter&lt;/strong&gt; — metadata that tells the agent what this skill is and when to use it:&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="nn"&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;analyzing-marketing-campaign&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;-"&lt;/span&gt;
  &lt;span class="s"&gt;Analyze weekly marketing campaign performance from CSV or BigQuery data.&lt;/span&gt;
  &lt;span class="s"&gt;Use when the user asks about campaign metrics, funnel analysis, ROAS,&lt;/span&gt;
  &lt;span class="s"&gt;or budget reallocation.&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; are the only two required fields. The name must be 1-64 characters, lowercase with hyphens, and should match the folder name. The description (up to 1,024 characters) is mission critical — it's how the agent detects when to activate the skill. Include trigger keywords and describe not just what the skill does, but &lt;em&gt;when&lt;/em&gt; to use it.&lt;/p&gt;

&lt;p&gt;Optional frontmatter fields include &lt;code&gt;license&lt;/code&gt;, &lt;code&gt;compatibility&lt;/code&gt; (environment requirements), &lt;code&gt;metadata&lt;/code&gt; (arbitrary key-value pairs like author and version), and &lt;code&gt;allowed-tools&lt;/code&gt; (pre-approved tool list for security-conscious environments).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Markdown Body&lt;/strong&gt; — the actual instructions, as detailed as you need:&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="gh"&gt;# Weekly Campaign Analysis&lt;/span&gt;

&lt;span class="gu"&gt;## Input Requirements&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Campaign data with columns: date, campaign_name, impressions, clicks, conversions
&lt;span class="p"&gt;-&lt;/span&gt; Date range must be specified

&lt;span class="gu"&gt;## Step 1: Data Quality Check&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Verify no missing values in required columns
&lt;span class="p"&gt;-&lt;/span&gt; Flag any anomalies (CTR &amp;gt; 20%, negative values, etc.)

&lt;span class="gu"&gt;## Step 2: Funnel Analysis&lt;/span&gt;
Calculate against these benchmarks:
| Metric | Benchmark |
|--------|-----------|
| CTR    | 2.5%      |
| CVR    | 3.0%      |

&lt;span class="gu"&gt;## Budget Reallocation&lt;/span&gt;
Only when the user asks about budget reallocation, read:
&lt;span class="sb"&gt;`references/budget_reallocation_rules.md`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that last line — the budget reallocation rules live in a separate file that &lt;strong&gt;only loads when the user asks about it&lt;/strong&gt;. That is progressive disclosure in action. Instead of cramming everything into the context window upfront, the skill tells the agent where to find additional information and when to fetch it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optional Directories
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Directory&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scripts/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Executable code loaded and run on demand&lt;/td&gt;
&lt;td&gt;Python data analysis, Bash automation, JS utilities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;references/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Additional documentation loaded when needed&lt;/td&gt;
&lt;td&gt;Domain-specific rules, style guides, API docs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;assets/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Templates, images, and data files&lt;/td&gt;
&lt;td&gt;Output templates, logos, schema files, sample data&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Skills aren't limited to text — they can include scripts in Python, Bash, or JavaScript that the agent executes as part of the workflow. A PDF processing skill might include Python scripts for form extraction. A data analysis skill might include diagnostic scripts and visualization code. The agent loads and runs these only when the workflow requires it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Skills Fit in the Agent Ecosystem
&lt;/h2&gt;

&lt;p&gt;With so many technologies in the AI toolkit — MCP, skills, tools, subagents — understanding when to use what is essential. Here is how they all fit together:&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%2Fb7b815bg1a76g1cun6ck.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%2Fb7b815bg1a76g1cun6ck.png" alt="Screenshot 2026-02-06 at 1.11.42 PM.png" width="800" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The analogy from the course captures it perfectly: &lt;strong&gt;tools are the hammer, saw, and nails. Skills are the knowledge of how to build a bookshelf.&lt;/strong&gt; The tools provide the raw capabilities (filesystem access, bash execution, API calls). Skills provide the procedural knowledge and domain expertise to use those tools in a specific, repeatable way.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Prompts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Atomic unit of communication&lt;/td&gt;
&lt;td&gt;One-off instructions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tools&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low-level capabilities (bash, filesystem, APIs)&lt;/td&gt;
&lt;td&gt;The building blocks that power everything&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MCP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Connects agents to external systems and data&lt;/td&gt;
&lt;td&gt;Database access, Google Drive, Slack, any external service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Skills&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Packaged workflows with domain expertise&lt;/td&gt;
&lt;td&gt;Repeatable tasks, team-wide processes, predictable output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Subagents&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Isolated execution with own context window&lt;/td&gt;
&lt;td&gt;Parallel tasks, specialized analysis, fine-grained permissions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  How They Compose Together
&lt;/h3&gt;

&lt;p&gt;The real power emerges when you combine these components. Consider a workflow that chains three skills with an MCP server:&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%2Ff3cgajeolz9bz4m18sco.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%2Ff3cgajeolz9bz4m18sco.png" alt="Screenshot 2026-02-06 at 1.14.27 PM.png" width="800" height="332"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this flow, the &lt;strong&gt;MCP server&lt;/strong&gt; provides the connection to BigQuery. The &lt;strong&gt;marketing analysis skill&lt;/strong&gt; defines &lt;em&gt;how&lt;/em&gt; to analyze the data (benchmarks, formulas, allocation rules). The &lt;strong&gt;brand guidelines skill&lt;/strong&gt; defines &lt;em&gt;how things should look&lt;/em&gt; (colors, typography, logos). The &lt;strong&gt;built-in PowerPoint skill&lt;/strong&gt; handles the &lt;em&gt;document generation&lt;/em&gt;. Each skill loads only its own context, only when needed.&lt;/p&gt;

&lt;p&gt;You can also dispatch &lt;strong&gt;subagents&lt;/strong&gt; alongside skills. A main agent can spawn subagents — each with their own context window and specific skill access — to parallelize work. For instance, one subagent analyzes customer interviews while another processes survey data, both using the same "customer insights" skill but running in parallel. The parent agent collects and synthesizes their results.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to Use What
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Reach For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Need data from an external database or API&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;MCP&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need a repeatable, documented workflow&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Skills&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need to teach the agent your company's specific process&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Skills&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need parallel execution with isolated context&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Subagents&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need real-time access to Google Drive / Slack / etc.&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;MCP&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need specialized analysis that shouldn't pollute main context&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Subagents + Skills&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need a one-off quick task&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Just a prompt&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Use skills for procedural, predictable workflows. Use subagents for full agentic logic only when necessary for specialized or parallel tasks. Use MCP for any external data or tooling. And remember — skills, subagents, and MCP are composable. The sweet spot is usually a combination of all three.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built-In Skills and the Skills Repository
&lt;/h2&gt;

&lt;p&gt;Anthropic ships several production-ready skills at &lt;a href="https://github.com/anthropics/skills" rel="noopener noreferrer"&gt;github.com/anthropics/skills&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Document Skills&lt;/strong&gt; (built into Claude AI, always active):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;What It Does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Excel&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Create, edit, and analyze &lt;code&gt;.xlsx&lt;/code&gt; spreadsheets with formatting and charts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PowerPoint&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Generate presentations with custom layouts, colors, typography&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Word&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Create formatted &lt;code&gt;.docx&lt;/code&gt; documents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PDF&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Extract text, fill forms, merge PDFs, convert to images&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Example Skills&lt;/strong&gt; (can be toggled on in Settings &amp;gt; Capabilities):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;What It Does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Skill Creator&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Meta-skill that programmatically creates new skills following best practices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Code Review&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Structured code review with configurable standards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data Analysis&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Statistical analysis workflows&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;strong&gt;skill-creator&lt;/strong&gt; is particularly useful — it's a skill that creates skills. It includes Python scripts for initializing the folder structure, packaging into a zip, and validating against best practices. It is enabled by default and can save significant time when building new skills.&lt;/p&gt;

&lt;p&gt;In Claude Code, you can install skills from the marketplace:&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 the Anthropic skills marketplace&lt;/span&gt;
/plugins marketplace add anthropics/skills

&lt;span class="c"&gt;# Verify installed skills&lt;/span&gt;
/skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Partner-built skills from companies like Atlassian, Figma, Canva, Stripe, Notion, and Zapier are also available through the skills directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Build a Skill: Architecture Walkthrough
&lt;/h2&gt;

&lt;p&gt;The best way to understand how all the pieces of a skill fit together is to look at a real-world example. Let's walk through the architecture of a &lt;strong&gt;Strapi Configuration Skill&lt;/strong&gt; — a skill that scaffolds fully configured Strapi CMS projects with content types, seed data, public API access, and auto-populate middleware.&lt;/p&gt;

&lt;p&gt;The goal here is not to recreate this skill step-by-step, but to understand the architectural decisions and moving parts so you can apply these patterns to your own skills. For the full source code, check out the &lt;a href="https://github.com/anthropics/skills" rel="noopener noreferrer"&gt;repository&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Directory Structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;strapi-configuration/
├── SKILL.md                  # Core instructions (480 lines)
├── CLAUDE.md                 # Development context for contributors
├── README.md                 # User-facing documentation
├── scripts/                  # Empty — reserved for future helpers
├── references/               # Empty — reserved for future docs
└── templates/                # Domain-specific data files
    ├── blog.json             # ~22KB preset definition
    ├── ecommerce.json        # ~23KB preset definition
    ├── portfolio.json        # ~25KB preset definition
    └── restaurant.json       # ~24KB preset definition
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This structure shows several important patterns:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Standard directories are present even when empty.&lt;/strong&gt; The &lt;code&gt;scripts/&lt;/code&gt; and &lt;code&gt;references/&lt;/code&gt; directories exist as placeholders — they signal to other developers (and the agent) that the skill follows the standard layout and may grow into these directories.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Domain-specific directories are allowed.&lt;/strong&gt; The &lt;code&gt;templates/&lt;/code&gt; directory isn't part of the standard spec (&lt;code&gt;scripts/&lt;/code&gt;, &lt;code&gt;references/&lt;/code&gt;, &lt;code&gt;assets/&lt;/code&gt; are), but skills can include any additional directories that make sense for the domain. Here, each JSON template is a ~23KB definition containing content types, components, middleware configs, seed data, and permission rules for a specific project type.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Progressive disclosure is baked into the architecture.&lt;/strong&gt; Those four template files total ~94KB of data. None of it loads until the agent actually needs a specific template. When the user picks "blog," only &lt;code&gt;blog.json&lt;/code&gt; enters the context — the other three templates stay on disk. That's ~70KB of tokens saved.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Frontmatter: Your Skill's Elevator Pitch
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&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;strapi-configuration&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
  &lt;span class="s"&gt;Create a fully configured Strapi project with preconfigured content types,&lt;/span&gt;
  &lt;span class="s"&gt;seed data, public API access, and route-based middleware for default&lt;/span&gt;
  &lt;span class="s"&gt;population. Use when the user wants to scaffold a new Strapi project for&lt;/span&gt;
  &lt;span class="s"&gt;a specific use case such as blog, e-commerce, portfolio, or restaurant.&lt;/span&gt;
  &lt;span class="s"&gt;Supports preset templates and custom project descriptions.&lt;/span&gt;
&lt;span class="na"&gt;compatibility&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Requires Node.js and access to the strapi-core monorepo.&lt;/span&gt;
&lt;span class="na"&gt;allowed-tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Bash Read Write Edit Glob Grep&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;strapi&lt;/span&gt;
  &lt;span class="na"&gt;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;0.1.0"&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key decisions worth noting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;description&lt;/strong&gt; is packed with trigger keywords: "scaffold", "Strapi project", "blog", "e-commerce", "portfolio", "restaurant", "content types", "seed data." When a user says any of these words, the agent knows this skill is relevant&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;compatibility&lt;/code&gt;&lt;/strong&gt; declares the runtime requirements upfront — the agent (and the user) knows this needs Node.js before it even starts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;allowed-tools&lt;/code&gt;&lt;/strong&gt; explicitly declares which tools the skill will use. In security-conscious environments, this allows pre-authorization rather than prompting for each tool at runtime&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Instruction Body: A Deterministic Workflow
&lt;/h3&gt;

&lt;p&gt;The body of this SKILL.md is structured as 9 sequential steps, each with clear entry/exit criteria. Here is the high-level flow:&lt;/p&gt;


  
[video-steps.mp4](https://delicate-dawn-ac25646e6d.media.strapiapp.com/video_steps_10379ccc12.mp4)
  Your browser does not support the video tag.


&lt;p&gt;Several patterns make this workflow reliable:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Explicit branching.&lt;/strong&gt; Step 1 presents a selection menu and routes to either Step 2 (preset template) or Step 2b (custom generation). The branching is clearly marked — the agent never has to guess which path to follow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code templates, not descriptions.&lt;/strong&gt; Rather than saying "create a controller," the skill provides the exact TypeScript code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;factories&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strapi/strapi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;factories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCoreController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;api::&amp;lt;name&amp;gt;.&amp;lt;name&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This eliminates guesswork and ensures the generated code actually works with the target framework. The skill includes templates for controllers, services, routes (with and without middleware), middleware files, component schemas, and the entire seed script.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Progressive file loading.&lt;/strong&gt; Step 2 says "Read the template JSON file at &lt;code&gt;templates/&amp;lt;preset&amp;gt;.json&lt;/code&gt;." It doesn't embed the template data in the SKILL.md — it tells the agent where to find it and what structure to expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;The template contains:
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`contentTypes`&lt;/span&gt; - Collection types and single types (schemas)
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`components`&lt;/span&gt; - Reusable component definitions
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`middlewares`&lt;/span&gt; - Route-based populate middleware configs
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`seedData`&lt;/span&gt; - Sample data entries for each content type
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`publicPermissions`&lt;/span&gt; - Which API endpoints to make publicly accessible
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The custom path generates data dynamically.&lt;/strong&gt; Step 2b is a mini-workflow within the workflow — it gathers requirements, designs a content model following patterns from existing templates, generates seed data, configures middleware, sets permissions, and optionally saves the result as a reusable template for next time. This demonstrates how skills can handle both deterministic paths (presets) and creative ones (custom generation) within the same workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Supporting Files: CLAUDE.md and README.md
&lt;/h3&gt;

&lt;p&gt;This skill includes two additional files that aren't part of the agent skills spec but demonstrate good practice:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt;&lt;/strong&gt; captures development context — the directory structure, key conventions for the Strapi schema format, CLI testing commands, common pitfalls (like &lt;code&gt;draftAndPublish&lt;/code&gt; needing to be nested inside &lt;code&gt;options&lt;/code&gt;, not at the top level), and future improvements. This is valuable for both the agent when it needs to modify the skill and for human developers maintaining it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/strong&gt; is user-facing documentation — installation instructions, quick start examples, usage for each preset, and what gets generated. It explains how to install the skill at project scope (&lt;code&gt;&amp;lt;project&amp;gt;/.claude/skills/&lt;/code&gt;) or globally (&lt;code&gt;~/.claude/skills/&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  What Makes This Skill Work Well
&lt;/h3&gt;

&lt;p&gt;Looking at the architecture holistically, several design decisions stand out:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The description is rich with trigger keywords&lt;/strong&gt; — the agent can match user intent to this skill reliably&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Steps are numbered and sequential&lt;/strong&gt; — there is no ambiguity about execution order&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code templates are exact, not described&lt;/strong&gt; — pattern matching produces reliable output&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Large data lives in files, not instructions&lt;/strong&gt; — 94KB of template data stays on disk until needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Branching is explicit&lt;/strong&gt; — preset vs. custom paths are clearly defined&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The skill stays under 500 lines&lt;/strong&gt; — keeping the main instructions concise and deferring detail to external files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge cases are handled&lt;/strong&gt; — what if no path is provided? What if the user picks custom? What if they want to save the result as a template?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is the skill in action.&lt;/p&gt;


  
[video-steps.mp4](https://delicate-dawn-ac25646e6d.media.strapiapp.com/video_steps_10379ccc12.mp4)
  Your browser does not support the video tag.


&lt;p&gt;&lt;strong&gt;note:&lt;/strong&gt; this is still work in progress, but once it is ready, I will share the repo here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices for Building Your Own Skills
&lt;/h2&gt;

&lt;p&gt;Based on the official specification, Anthropic's engineering blog, and patterns observed across production skills:&lt;/p&gt;

&lt;h3&gt;
  
  
  Naming and Description
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name&lt;/strong&gt;: Use lowercase letters, numbers, and hyphens. Follow the &lt;code&gt;verb-ing + noun&lt;/code&gt; pattern: &lt;code&gt;analyzing-marketing-campaign&lt;/code&gt;, &lt;code&gt;generating-practice-questions&lt;/code&gt;. Don't use reserved keywords like "claude" or "anthropic." Max 64 characters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Description&lt;/strong&gt;: This is how the agent decides when to use your skill. Describe both &lt;em&gt;what it does&lt;/em&gt; and &lt;em&gt;when to use it&lt;/em&gt;. Include trigger keywords. Max 1,024 characters&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Structure and Content
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keep SKILL.md under 500 lines.&lt;/strong&gt; Move detailed content into &lt;code&gt;references/&lt;/code&gt;, &lt;code&gt;scripts/&lt;/code&gt;, or &lt;code&gt;assets/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use step-by-step instructions.&lt;/strong&gt; Number your steps. Specify edge cases. Be clear about what to skip and why&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provide code templates, not descriptions.&lt;/strong&gt; Agents produce more reliable output when they can pattern-match against exact examples&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use progressive disclosure aggressively.&lt;/strong&gt; If a piece of information is only needed 20% of the time, put it in a reference file&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use forward slashes&lt;/strong&gt; in all file paths, even on Windows&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Degree of Freedom
&lt;/h3&gt;

&lt;p&gt;Think about how much freedom to give the agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Low freedom&lt;/strong&gt; for processes that must be followed exactly (compliance checks, data pipelines, test procedures)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High freedom&lt;/strong&gt; for creative outputs where variety is desirable (design, writing, brainstorming)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Testing and Evaluation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Run your skill through the &lt;strong&gt;skill-creator&lt;/strong&gt; skill to score it against best practices&lt;/li&gt;
&lt;li&gt;Write test cases like unit tests: define input queries, expected behaviors, and output formats&lt;/li&gt;
&lt;li&gt;Test across all models you plan to use — different models may interpret instructions differently&lt;/li&gt;
&lt;li&gt;Gather human feedback and iterate&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where Skills Can Live
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;Location&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Project&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;project&amp;gt;/.claude/skills/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Team-specific workflows tied to a codebase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;User&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.claude/skills/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Personal skills available across all projects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Claude AI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Settings &amp;gt; Capabilities &amp;gt; Skills&lt;/td&gt;
&lt;td&gt;Upload as zip, available in the web UI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Marketplace&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;anthropics/skills&lt;/code&gt; on GitHub&lt;/td&gt;
&lt;td&gt;Community and official skills&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Open Standard: Cross-Platform Portability
&lt;/h2&gt;

&lt;p&gt;Agent Skills follow the same playbook Anthropic used with the Model Context Protocol: build a specification that solves a real interoperability problem, release it as an open standard, and let ecosystem adoption create network effects.&lt;/p&gt;

&lt;p&gt;The platforms that have adopted the standard now include Claude, Claude Code, OpenAI Codex, Gemini CLI, GitHub Copilot, VS Code, Cursor, Roo Code, Amp, Goose, Mistral AI, Databricks, and many others — over 26 at last count. Partner-built skills from Atlassian, Figma, Canva, Stripe, Notion, and Zapier are available at launch.&lt;/p&gt;

&lt;p&gt;The specification and reference library live at &lt;a href="https://github.com/agentskills/agentskills" rel="noopener noreferrer"&gt;github.com/agentskills/agentskills&lt;/a&gt;. The reference library includes a validation tool you can use to check your skills against the spec:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;skills-ref validate ./my-skill
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Getting Started: Your First Skill in 5 Minutes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Create the folder:&lt;/strong&gt;&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="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; .claude/skills/my-first-skill
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Create &lt;code&gt;SKILL.md&lt;/code&gt;:&lt;/strong&gt;&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="nn"&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;my-first-skill&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
  &lt;span class="s"&gt;Describe what your skill does and when to trigger it.&lt;/span&gt;
  &lt;span class="s"&gt;Include keywords that users are likely to say.&lt;/span&gt;
&lt;span class="s"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# My First Skill&lt;/span&gt;

&lt;span class="gu"&gt;## Steps&lt;/span&gt;

&lt;span class="gu"&gt;### Step 1: [First thing to do]&lt;/span&gt;
[Detailed instructions...]

&lt;span class="gu"&gt;### Step 2: [Second thing to do]&lt;/span&gt;
[Detailed instructions...]

&lt;span class="gu"&gt;## Output Format&lt;/span&gt;
[What the final result should look like]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Test it&lt;/strong&gt; by starting a new session and asking a question that matches your skill's description.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Iterate.&lt;/strong&gt; Run it through the skill-creator for best-practice feedback, or validate it with the reference library. Refine based on actual usage.&lt;/p&gt;

&lt;p&gt;Whether you're automating a weekly report, encoding your team's code review process, building a project scaffolding tool, or packaging brand guidelines for consistent design output — skills transform one-off prompts into durable, shareable, composable knowledge that works everywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Citations&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agent Skills Specification: &lt;a href="https://agentskills.io/specification" rel="noopener noreferrer"&gt;https://agentskills.io/specification&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Introducing Agent Skills (Anthropic): &lt;a href="https://www.anthropic.com/news/skills" rel="noopener noreferrer"&gt;https://www.anthropic.com/news/skills&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Equipping Agents for the Real World with Agent Skills: &lt;a href="https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills" rel="noopener noreferrer"&gt;https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Skill Authoring Best Practices (Claude Docs): &lt;a href="https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices" rel="noopener noreferrer"&gt;https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Agent Skills Overview (Claude API Docs): &lt;a href="https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview" rel="noopener noreferrer"&gt;https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Agent Skills GitHub Repository: &lt;a href="https://github.com/anthropics/skills" rel="noopener noreferrer"&gt;https://github.com/anthropics/skills&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Agent Skills Open Standard Repository: &lt;a href="https://github.com/agentskills/agentskills" rel="noopener noreferrer"&gt;https://github.com/agentskills/agentskills&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OpenAI Codex Skills Documentation: &lt;a href="https://developers.openai.com/codex/skills/" rel="noopener noreferrer"&gt;https://developers.openai.com/codex/skills/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;VS Code Agent Skills: &lt;a href="https://code.visualstudio.com/docs/copilot/customization/agent-skills" rel="noopener noreferrer"&gt;https://code.visualstudio.com/docs/copilot/customization/agent-skills&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>aiskills</category>
      <category>mcp</category>
      <category>aiagents</category>
    </item>
    <item>
      <title>How to Build a Strapi Plugin That Extends the Admin and Backend</title>
      <dc:creator>Theodore Kelechukwu Onyejiaku</dc:creator>
      <pubDate>Fri, 13 Mar 2026 09:49:03 +0000</pubDate>
      <link>https://dev.to/strapi/how-to-build-a-strapi-plugin-that-extends-the-admin-and-backend-2enj</link>
      <guid>https://dev.to/strapi/how-to-build-a-strapi-plugin-that-extends-the-admin-and-backend-2enj</guid>
      <description>&lt;p&gt;Whether you’re building a product solo, working with clients, or maintaining a CMS for a growing team, the starting point is usually the same.&lt;/p&gt;

&lt;p&gt;You define content types, expose them through APIs, and consume that content in your application. Editors work in a structured interface, developers get predictable data, and the CMS stays focused on content.&lt;/p&gt;

&lt;p&gt;That already covers a lot — but rarely everything.&lt;/p&gt;

&lt;p&gt;Real products quickly introduce needs that go beyond content modeling: custom backend logic, admin workflows, or UI extensions that reflect how content is actually created and reviewed. When that happens, it’s important to know whether the CMS can adapt to additional requirements and scale with the product. This is about avoiding situations where small needs later force difficult architectural decisions.&lt;/p&gt;

&lt;p&gt;This is where Strapi starts to matter.&lt;/p&gt;

&lt;p&gt;Strapi is designed to be extended through plugins. Plugins are the mechanism Strapi provides to adapt the CMS to your project: adding backend logic, extending APIs, and customizing the admin interface when needed — without pushing those needs outside the CMS you already rely on.&lt;/p&gt;

&lt;p&gt;In this article, we’ll make this concrete by building a small but real Strapi plugin: a todo list that integrates directly into the admin panel and attaches itself to any content type.&lt;/p&gt;

&lt;p&gt;Let’s get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing Strapi
&lt;/h2&gt;

&lt;p&gt;To get started, you'll need a Strapi project to work with. &lt;br&gt;
You can follow &lt;a href="https://docs.strapi.io/cms/quick-start" rel="noopener noreferrer"&gt;the official quick-start guide&lt;/a&gt;, or simply run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-strapi-app@latest my-strapi-project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




  


&lt;p&gt;Once the installation is finished you can navigate into the newly created folder and start Strapi in development mode:&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="nb"&gt;cd &lt;/span&gt;my-strapi-project &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run develop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When Strapi starts, it will open in your browser at &lt;a href="http://localhost:1337/admin/auth/register-adminhttp://localhost:1337/admin/auth/register-admin" rel="noopener noreferrer"&gt;http://localhost:1337/admin/auth/register-admin&lt;/a&gt;. The first screen prompts you to create an admin user for your application. Once that’s done, you’ll land in the Strapi admin and can start building.&lt;/p&gt;

&lt;p&gt;At this point, you have a working Strapi project and a clean starting point for plugin development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bootstrapping a Strapi plugin with the Plugin SDK
&lt;/h2&gt;

&lt;p&gt;After you’ve installed Strapi and created your admin user you are now ready to start developing your plugin! &lt;/p&gt;

&lt;p&gt;Strapi provides an &lt;a href="https://docs.strapi.io/cms/plugins-development/plugin-sdk" rel="noopener noreferrer"&gt;official plugin SDK&lt;/a&gt; that takes care of structure and conventions for you. From the root of your project, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @strapi/sdk-plugin init todo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command launches an interactive CLI that asks a few questions about your plugin. Based on your answers, it generates a blank plugin inside &lt;code&gt;src/plugins/todo&lt;/code&gt;.&lt;/p&gt;


  


&lt;p&gt;Once the plugin has been bootstrapped, you can enable it by updating &lt;code&gt;./config/plugins.ts&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;todo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;enabled&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;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/plugins/todo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that in place, Strapi will load your plugin at startup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building your plugin
&lt;/h3&gt;

&lt;p&gt;At this point, Strapi is running and your plugin is enabled — but there’s one important detail to be aware of before you start writing code.&lt;/p&gt;

&lt;p&gt;When you run &lt;code&gt;strapi develop&lt;/code&gt;, Strapi does &lt;strong&gt;not&lt;/strong&gt; compile plugin code for you. Plugin code is built separately, which means that while developing your plugin you’ll need to run an additional build process.&lt;/p&gt;

&lt;p&gt;From the plugin directory, start the watch command:&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="nb"&gt;cd &lt;/span&gt;src/plugins/todo &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run watch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This watches your plugin files and rebuilds them as you make changes.&lt;/p&gt;

&lt;p&gt;This separation is intentional. In Strapi, plugins are treated as &lt;strong&gt;isolated units&lt;/strong&gt;, with their own dependencies and build lifecycle, rather than as hidden parts of the main application. Keeping plugin builds explicit makes the boundary between the application and its extensions clear, and avoids coupling plugin development to the core dev process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a content type
&lt;/h3&gt;

&lt;p&gt;In Strapi, &lt;strong&gt;everything you store is a content type&lt;/strong&gt;. Whether it’s an Article, a Product, or a User profile, the data lives in a model with a schema — and Strapi uses that schema to generate the database structure, admin behavior, and API layer.&lt;/p&gt;

&lt;p&gt;A plugin doesn’t change that. Even if the UI and logic live inside the plugin, the data still needs a place to live.&lt;/p&gt;

&lt;p&gt;So if our todo plugin is going to store tasks, we need a content type for them — and we’ll define it &lt;strong&gt;inside the plugin&lt;/strong&gt;, so the whole feature (UI + backend + data model) stays self-contained.&lt;/p&gt;

&lt;p&gt;The easiest way to do this is with the Strapi generator CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run strapi generate content-type
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this command will open up another interactive CLI. In there you can specify the name of the content type as well as add some attributes. &lt;/p&gt;

&lt;p&gt;When prompted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;name the content type &lt;code&gt;task&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;add attributes: &lt;code&gt;name&lt;/code&gt; (text) and &lt;code&gt;done&lt;/code&gt; (boolean)&lt;/li&gt;
&lt;li&gt;choose to add it to the existing &lt;code&gt;todo&lt;/code&gt; plugin&lt;/li&gt;
&lt;li&gt;allow the CLI to generate API-related files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don’t forget to select the option to add the model to your existing todo plugin and to bootstrap API related files.&lt;/p&gt;


  


&lt;p&gt;Once the generator finishes, you’ll see new files for the &lt;code&gt;task&lt;/code&gt; content type: a schema, service, controller, and routes — all scoped to the plugin. &lt;/p&gt;

&lt;p&gt;By default, &lt;strong&gt;every content type in Strapi is exposed through the Content API&lt;/strong&gt;. That’s usually what you want: content types are meant to be queried by frontends and external consumers.&lt;/p&gt;

&lt;p&gt;In this case, it’s not.&lt;/p&gt;

&lt;p&gt;Tasks are an &lt;strong&gt;internal, admin-only concern&lt;/strong&gt;. They exist purely to support editors while working in the admin panel, and should not be reachable from the public Content API.&lt;/p&gt;

&lt;p&gt;For that reason, we’ll move the generated routes from the Content API to the &lt;strong&gt;Admin API&lt;/strong&gt;, making the &lt;code&gt;task&lt;/code&gt; content type accessible only inside the admin context.&lt;/p&gt;

&lt;p&gt;To do that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Move the generated route file &lt;code&gt;src/plugins/todo/server/src/routes/content-api/task.ts&lt;/code&gt; to the &lt;code&gt;src/plugins/todo/server/src/routes/&lt;/code&gt;admin folder. You will then end up with the following file &lt;code&gt;src/plugins/todo/server/src/routes/admin/task.ts&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Task router
 */&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;factories&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strapi/strapi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;factories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCoreRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;plugin::todo.task&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Update  &lt;code&gt;src/plugins/todo/server/src/routes/admin/index.ts&lt;/code&gt; file to register those routes as admin routes
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./task&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;routes&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;ol&gt;
&lt;li&gt;Update  &lt;code&gt;src/plugins/todo/server/src/routes/content-api/index.ts&lt;/code&gt; to replace the content API routes with an empty definition
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&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;p&gt;This keeps the task API private to the admin context, which is exactly what we want.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Known issue with generated routes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When generating content types with the CLI, you may run into a small TypeScript edge case when registering admin routes.&lt;/p&gt;

&lt;p&gt;You might see an error like:&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="o"&gt;[&lt;/span&gt;ERROR] server/src/routes/admin/index.ts:4:15 - TS2488:
Type&lt;span class="s1"&gt;'Route[] | (() =&amp;gt; Route[])'&lt;/span&gt; must have a&lt;span class="s1"&gt;'[Symbol.iterator]()'&lt;/span&gt; method that returns an iterator.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a typing issue only — the routes work correctly at runtime. You can safely fix it by adding a &lt;code&gt;@ts-expect-error&lt;/code&gt; when spreading the routes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./task&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;exportdefault &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="c1"&gt;// @ts-expect-error&lt;/span&gt;
&lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;routes&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;h3&gt;
  
  
  Polymorphic relation
&lt;/h3&gt;

&lt;p&gt;Each task should be associated with whatever content entry it belongs to — articles, pages, users, or any other type. To support that, the relation needs to be &lt;em&gt;polymorphic&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In this context, &lt;em&gt;polymorphic&lt;/em&gt; simply means the relation isn’t tied to one specific content type. A task can point to different kinds of entries, depending on where it’s created in the admin panel.&lt;/p&gt;

&lt;p&gt;Update the &lt;code&gt;src/plugins/todo/server/src/content-types/task/schema.json&lt;/code&gt; schema to add a &lt;code&gt;morphToMany&lt;/code&gt; relation called &lt;code&gt;related&lt;/code&gt;,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"collectionType"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"collectionName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tasks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"singularName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"task"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"pluralName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"tasks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"displayName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Task"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"draftAndPublish"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"pluginOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"content-manager"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"visible"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"content-type-builder"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"visible"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"attributes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"done"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"boolean"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"related"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"relation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"relation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"morphToMany"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, the &lt;code&gt;task&lt;/code&gt; content type has three fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;done&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;related&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s all we need.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Fetching related tasks with a custom admin API route&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Now we need a way to fetch all tasks linked to the entry currently being edited. The catch is that Strapi doesn’t support filtering on polymorphic relations out of the box (see &lt;a href="https://docs.strapi.io/cms/faq#can-you-filter-andor-deep-filter-on-dynamic-zones-and-polymorphic-relations" rel="noopener noreferrer"&gt;the FAQ&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;So we’ll add a custom service method that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;looks up task IDs in the polymorphic relation table, then&lt;/li&gt;
&lt;li&gt;fetches the matching task documents.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Open &lt;code&gt;server/src/services/task.ts&lt;/code&gt; and extend the core service with &lt;code&gt;findRelatedTasks&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Task service
*/&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;factories&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strapi/strapi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;factories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCoreService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;plugin::todo.task&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,({&lt;/span&gt; &lt;span class="nx"&gt;strapi&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;findRelatedTasks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;relatedId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="na"&gt;relatedType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;taskIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;strapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tasks_related_mph&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;task_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="na"&gt;related_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;relatedId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;related_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;relatedType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;strapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;plugin::todo.task&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;taskIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;task_id&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;task_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;});&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;p&gt;Next, expose this method through a custom controller action. Open &lt;code&gt;server/src/controllers/task.ts&lt;/code&gt; and add &lt;code&gt;findRelatedTasks&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Task controller
 */&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;factories&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strapi/strapi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;factories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCoreController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;plugin::todo.task&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,({&lt;/span&gt; &lt;span class="nx"&gt;strapi&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="nf"&gt;asyncfindRelatedTasks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;relatedId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;relatedType&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;strapi&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;plugin::todo.task&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findRelatedTasks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;relatedId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;relatedType&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;;&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;p&gt;Finally, register an admin route that calls this controller. Update &lt;code&gt;server/src/routes/admin/index.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./task&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="c1"&gt;// @ts-expect-error&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/tasks/related/:relatedType/:relatedId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;task.findRelatedTasks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&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;p&gt;With this in place, the admin panel can request related tasks for the current entry using a single endpoint — which we’ll use later when building the sidebar UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Cleaning up the admin UI&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Now that the backend pieces are in place, it’s time to prepare the admin side.&lt;/p&gt;

&lt;p&gt;The generated content type appears in the Content Manager and Content-Type Builder by default.&lt;/p&gt;

&lt;p&gt;Because tasks are meant to be managed through the plugin UI (not as standalone entries), we’ll hide the &lt;code&gt;Task&lt;/code&gt; content type from the Content Manager and the Content-Type Builder. &lt;a href="https://docs.strapi.io/cms/plugins-development/guides/create-components-for-plugins#making-components-visible-in-the-admin-panel" rel="noopener noreferrer"&gt;You can do this by updating the schema of the Task content type at &lt;code&gt;src/plugins/todo/server/src/content-types/task/schema.json&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pluginOptions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content-manager&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;visible&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content-type-builder&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;visible&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="p"&gt;}&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;p&gt;The plugin also comes with its own menu entry and page. Since this plugin only adds functionality &lt;em&gt;inside&lt;/em&gt; the Content Manager, you can remove:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the plugin icon by removing &lt;code&gt;src/plugins/todo/admin/src/components/PluginIcon.tsx&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;the plugin pages by deleting &lt;code&gt;src/plugins/todo/admin/src/pages/&lt;/code&gt; folder&lt;/li&gt;
&lt;li&gt;the corresponding menu registration from the &lt;code&gt;src/plugins/todo/admin/src/index.ts&lt;/code&gt; file&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Creating the sidebar panel
&lt;/h3&gt;

&lt;p&gt;The core of this plugin lives in the Content Manager edit view. We’ll add a custom sidebar component that will appear within the edit screen of a content document and will list all the tasks related to it. &lt;/p&gt;

&lt;p&gt;To do so, you’ll have to create a new component in your plugin. We’ll create that at &lt;code&gt;src/plugins/todo/admin/src/components/TodoPanel.tsx&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;unstable_useContentManagerContext&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;useContentManagerContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PanelComponent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strapi/content-manager/strapi-admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TodoPanel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PanelComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Todo list&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; All my todos will be here. &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;TodoPanel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once we’ve created the panel component we can now register it using the &lt;code&gt;addEditViewSidePanel&lt;/code&gt; API provided by the Content Manager plugin. We do this in the bootstrap function of &lt;code&gt;src/plugins/todo/admin/src/index.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;TodoPanel&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./components/TodoPanel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-manager&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;apis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEditViewSidePanel&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;TodoPanel&lt;/span&gt;&lt;span class="p"&gt;]);&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;p&gt;Once registered, the panel shows up automatically when editing entries.&lt;/p&gt;


  


&lt;h2&gt;
  
  
  &lt;strong&gt;Fetching and displaying tasks&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;At this point, the plugin has everything it needs on the backend:&lt;/p&gt;

&lt;p&gt;a content type, admin-only routes, and a custom endpoint to fetch tasks related to a specific entry.&lt;/p&gt;

&lt;p&gt;What’s missing is the admin-side logic that ties it all together.&lt;/p&gt;

&lt;p&gt;Inside the Content Manager edit view, we want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fetch the tasks related to the currently edited entry,&lt;/li&gt;
&lt;li&gt;display them inline in the sidebar,&lt;/li&gt;
&lt;li&gt;and update their state without reloading the page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To handle data fetching, caching, and updates cleanly, we’ll use &lt;a href="https://tanstack.com/query" rel="noopener noreferrer"&gt;&lt;code&gt;@tanstack/react-query&lt;/code&gt;&lt;/a&gt;. It fits well here because it lets us express &lt;em&gt;“this list depends on the current document”&lt;/em&gt; and takes care of refetching when data changes.&lt;/p&gt;

&lt;p&gt;First, install it as a dependency of the plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @tanstack/react-query
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Wiring React Query into the panel
&lt;/h3&gt;

&lt;p&gt;React Query works through a shared QueryClient. We'll create one and wrap the panel content in a QueryClientProvider by editing &lt;code&gt;./plugin-todo/admin/components/TodoPanel.tsx&lt;/code&gt;, then move the task-specific logic into a dedicated TaskList component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ./plugin-todo/admin/components/TodoPanel.tsx&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;unstable_useContentManagerContext&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;useContentManagerContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PanelComponent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strapi/content-manager/strapi-admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;QueryClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;QueryClientProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tanstack/react-query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;TaskList&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./TodoList&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryClient&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;QueryClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TodoPanel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PanelComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Todo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;QueryClientProvider&lt;/span&gt; &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TaskList&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;QueryClientProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;TodoPanel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Fetching and updating tasks
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;TaskList&lt;/code&gt; component is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;determining &lt;em&gt;which&lt;/em&gt; entry is currently being edited,&lt;/li&gt;
&lt;li&gt;fetching its related tasks via the custom admin endpoint,&lt;/li&gt;
&lt;li&gt;rendering them as a checklist,&lt;/li&gt;
&lt;li&gt;and updating task state when a checkbox is toggled.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ./plugin-todo/admin/components/TodoList.tsx&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;unstable_useContentManagerContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useFetchClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strapi/strapi/admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useMutation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useQueryClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tanstack/react-query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Checkbox&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strapi/design-system&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TaskList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;put&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFetchClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;unstable_useContentManagerContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQueryClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tasks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;queryFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/todo/tasks/related/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updateTaskMutation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMutation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;mutationFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;done&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/todo/tasks/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;onSuccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invalidateQueries&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tasks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleCheckboxChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;currentDone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;updateTaskMutation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mutate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;done&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;currentDone&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;done&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;marginTop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;12px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentId&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Checkbox&lt;/span&gt; 
            &lt;span class="na"&gt;checked&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="na"&gt;onCheckedChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&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;handleCheckboxChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Checkbox&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;TaskList&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this stage, the request to &lt;code&gt;/todo/tasks/related&lt;/code&gt; is working — but the list is empty. That’s expected: we haven’t created any tasks yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating tasks from the admin panel
&lt;/h3&gt;

&lt;p&gt;To add tasks directly from the Content Manager, we’ll use a modal built with the &lt;a href="https://design-system.strapi.io/" rel="noopener noreferrer"&gt;Strapi Design System&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The modal contains a small form that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;captures the task name,&lt;/li&gt;
&lt;li&gt;associates it with the currently edited document,&lt;/li&gt;
&lt;li&gt;creates the task through the plugin’s POST route,&lt;/li&gt;
&lt;li&gt;and refreshes the task list when the operation succeeds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create a new component at &lt;code&gt;admin/src/components/TodoModal.tsx&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ./plugin-todo/admin/components/TodoModal.tsx&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strapi/design-system&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Field&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strapi/design-system&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Modal&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strapi/design-system&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;unstable_useContentManagerContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useFetchClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strapi/strapi/admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useMutation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useQueryClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tanstack/react-query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;onOpenChange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TodoModal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onOpenChange&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;taskName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTaskName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;unstable_useContentManagerContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFetchClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQueryClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createTaskMutation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMutation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;mutationFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/todo/tasks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;onSuccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invalidateQueries&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tasks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nf"&gt;setTaskName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;onOpenChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;submitForm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;createTaskMutation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mutate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; 
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;taskName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="na"&gt;related&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="na"&gt;__type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Modal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Root&lt;/span&gt; &lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isOpen&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onOpenChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onOpenChange&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Modal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Modal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Modal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Create task&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Modal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Modal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Modal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Root&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"task"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Task&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt; 
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;taskName&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
              &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ChangeEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;setTaskName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Root&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Modal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Modal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Footer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Modal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"tertiary"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Cancel&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Modal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; 
            &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;submitForm&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; 
            &lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;createTaskMutation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isPending&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;taskName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;createTaskMutation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isPending&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            Confirm
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Modal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Footer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Modal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Modal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Root&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;TodoModal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important detail here is the &lt;code&gt;related&lt;/code&gt; field: we attach the task to the current document using its model type and ID, which makes the polymorphic relation work seamlessly across content types.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connecting everything in the panel
&lt;/h3&gt;

&lt;p&gt;Finally, we add a button to the sidebar panel that opens the modal and renders the task list below it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ./plugin-todo/admin/components/TodoList.tsx&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;unstable_useContentManagerContext&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;useContentManagerContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PanelComponent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strapi/content-manager/strapi-admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;TextButton&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strapi/design-system&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Plus&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strapi/icons&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;TaskList&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./TodoList&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;QueryClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;QueryClientProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tanstack/react-query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;TodoModal&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./TodoModal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryClient&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;QueryClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TodoPanel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PanelComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;modalOpen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setModalOpen&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useContentManagerContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Todo List&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;QueryClientProvider&lt;/span&gt; &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TextButton&lt;/span&gt;
            &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&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;setModalOpen&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="si"&gt;}&lt;/span&gt;
            &lt;span class="na"&gt;startIcon&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Plus&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            Add todo
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;TextButton&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TodoModal&lt;/span&gt; &lt;span class="na"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;modalOpen&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onOpenChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;setModalOpen&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TaskList&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;QueryClientProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;TodoPanel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that in place, the plugin comes together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tasks appear next to content,&lt;/li&gt;
&lt;li&gt;tasks can be created and updated inline,&lt;/li&gt;
&lt;li&gt;and all the logic stays scoped to the plugin.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Refreshing the admin panel now shows a fully functional todo list embedded directly in the Content Manager.&lt;/p&gt;


  


&lt;h2&gt;
  
  
  Deploying to production with Strapi Cloud
&lt;/h2&gt;

&lt;p&gt;So far, everything we’ve done has been running locally. The final step is making sure the plugin is built and available in production — and then actually seeing it live.&lt;/p&gt;

&lt;p&gt;Because plugin code is built separately from the main Strapi app, it needs to be compiled as part of the deployment process. A simple way to ensure this happens automatically is to add a &lt;code&gt;postinstall&lt;/code&gt; script to the root &lt;code&gt;package.json&lt;/code&gt; of your Strapi project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"postinstall"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"cd src/plugins/todo &amp;amp;&amp;amp; npm install &amp;amp;&amp;amp; npm run build"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This guarantees that whenever your project installs dependencies — locally, in CI, or in production — the plugin is installed and built as well.&lt;/p&gt;

&lt;p&gt;If you’re using &lt;strong&gt;Strapi Cloud&lt;/strong&gt;, you don’t need any additional setup.&lt;/p&gt;

&lt;p&gt;Once your project is connected to Strapi Cloud, every push triggers a deployment where dependencies are installed and build steps are executed. With the &lt;code&gt;postinstall&lt;/code&gt; script in place, your plugin is compiled automatically during that process.&lt;/p&gt;

&lt;p&gt;After the deployment finishes, open your Strapi Cloud project, navigate to the Content Manager, open any entry, and you should see your todo sidebar panel — just like in local development.&lt;/p&gt;

&lt;p&gt;At this point, the plugin is running in production, inside the same admin your editors use every day.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Repo
&lt;/h3&gt;

&lt;p&gt;The complete code can be found here: &lt;a href="https://github.com/strapi-community/plugin-todo" rel="noopener noreferrer"&gt;https://github.com/strapi-community/plugin-todo&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Final words
&lt;/h3&gt;

&lt;p&gt;This plugin is intentionally small, but it mirrors how real Strapi extensions are built and shipped.&lt;/p&gt;

&lt;p&gt;You defined a data model, scoped it to a plugin, exposed only the APIs you needed, and extended the admin interface where editors actually work. Nothing lives outside the system, and nothing special is required to deploy it.&lt;/p&gt;

&lt;p&gt;That’s the core idea behind Strapi plugins: they let you adapt the CMS to your product without turning customization into a separate project.&lt;/p&gt;

&lt;p&gt;If you want to explore further, the full plugin code is &lt;a href="https://github.com/strapi-community/plugin-todo" rel="noopener noreferrer"&gt;available on GitHub&lt;/a&gt;, and the Strapi community is always a good place to go deeper — whether you’re refining an internal workflow or building something meant to be reused by others.&lt;/p&gt;

</description>
      <category>strapi</category>
      <category>strapiplugins</category>
      <category>plugins</category>
    </item>
    <item>
      <title>Building Production-Ready Strapi + Next.js 16 Applications: Lessons from NOTUM Technologies</title>
      <dc:creator>Theodore Kelechukwu Onyejiaku</dc:creator>
      <pubDate>Fri, 06 Feb 2026 12:56:51 +0000</pubDate>
      <link>https://dev.to/strapi/building-production-ready-strapi-nextjs-16-applications-lessons-from-notum-technologies-3idn</link>
      <guid>https://dev.to/strapi/building-production-ready-strapi-nextjs-16-applications-lessons-from-notum-technologies-3idn</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Who:&lt;/strong&gt; &lt;a href="https://www.notum.tech/" rel="noopener noreferrer"&gt;NOTUM Technologies&lt;/a&gt; is a digital agency based in Brno, Czech Republic, and has been a Strapi partner for 5+ years.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What:&lt;/strong&gt; They created an open-source Next.js + &lt;a href="https://www.notumstrapi.com/" rel="noopener noreferrer"&gt;Strapi starter template&lt;/a&gt; that saves 3-4 weeks of development time per project.&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%2Fxzk7j1in3iirhl696ap4.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%2Fxzk7j1in3iirhl696ap4.png" alt="notum-starter.png" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Highlights:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monorepo architecture with full TypeScript type safety between Strapi and Next.js&lt;/li&gt;
&lt;li&gt;Supports projects from small startups to enterprise clients with billions of visitors&lt;/li&gt;
&lt;li&gt;Includes automatic page hierarchy with recursive full-path generation&lt;/li&gt;
&lt;li&gt;Built-in redirect management when page structures change&lt;/li&gt;
&lt;li&gt;Docker-ready deployment for any platform (Azure, AWS, Heroku, etc.)&lt;/li&gt;
&lt;li&gt;Uses PNPM for better dependency management and smaller build sizes&lt;/li&gt;
&lt;li&gt;Server-side populate logic to avoid frontend complexity and URL length limitations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Notable Plugins Created:&lt;/strong&gt; Location picker for maps, &lt;a href="https://market.strapi.io/plugins/@notum-cz-strapi-plugin-record-locking" rel="noopener noreferrer"&gt;Record locking&lt;/a&gt; for multi-user editing&lt;/p&gt;




&lt;p&gt;This article is based on the follwing discussion. Just in case you don't have the time to watch you can do a quick read instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction: From Pain Point to Production Solution
&lt;/h2&gt;

&lt;p&gt;Every agency knows the struggle: a new client comes in, and you're starting from scratch—again. Copy the old project, strip out the irrelevant components, implement new ones, and hope you haven't introduced any bugs along the way. For NOTUM Technologies, this cycle became unsustainable once they hit a critical mass of clients.&lt;/p&gt;

&lt;p&gt;The solution? A comprehensive starter template that distills years of production experience into a single, battle-tested package. But this isn't just another tutorial project—it's the foundation that has powered enterprise applications serving billions of visitors.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Strapi? A Five-Year Partnership
&lt;/h2&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%2Fndaxfwb7i4ttm78448iq.webp" 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%2Fndaxfwb7i4ttm78448iq.webp" alt="68fa0769f5490b032ab21a01_hero-about-notum-p-800.webp" width="800" height="524"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;NOTUM's journey with Strapi began around 2018-2019 when they were evaluating CMS options. After extensive internal discussions, Strapi emerged as the winner for several compelling reasons:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flexibility at Scale:&lt;/strong&gt; Strapi works beautifully out of the box for simple websites, but it also allows deep customization through plugins and custom code. This dual nature makes it suitable for the wide variety of projects an agency encounters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plugin Ecosystem:&lt;/strong&gt; NOTUM has contributed significantly to the Strapi ecosystem, creating plugins like the location picker (for map-based selection and proximity filtering) and record locking (preventing users from accidentally overwriting each other's changes).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Community Building:&lt;/strong&gt; Beyond code contributions, NOTUM actively nurtures the local Strapi community through in-person meetups, including a V5 launch party. These gatherings provide valuable opportunities to share learnings and collect feedback directly from Strapi's team.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture: Why a Monorepo?
&lt;/h2&gt;

&lt;p&gt;One of the most consequential decisions in the template is its monorepo structure, powered by Turborepo. This wasn't a trendy choice—it was driven by a real technical need.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Type Safety Problem
&lt;/h3&gt;

&lt;p&gt;Strapi doesn't automatically expose TypeScript types to frontend applications. In traditional setups, developers either manually maintain duplicate type definitions or accept the risk of runtime errors when schemas change. Neither option scales well.&lt;/p&gt;

&lt;p&gt;By using a monorepo, NOTUM solved this elegantly: auto-generated types from Strapi are exposed to the entire workspace. When you change a content schema, those changes automatically propagate to the frontend application. No more runtime surprises, no more manual syncing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;note&lt;/strong&gt;: the Strapi team actively is working on improving DX experience to allow users to expose Strapi types to their frontend project.  We will keep you posted with updates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Package Structure
&lt;/h3&gt;

&lt;p&gt;The monorepo contains two main workspaces:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Apps Workspace:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;strapi&lt;/code&gt; - The backend CMS application&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ui&lt;/code&gt; - The Next.js frontend application&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Packages Workspace:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shared ESLint and TypeScript configurations&lt;/li&gt;
&lt;li&gt;A shared data package for configuration variables (like supported locales)&lt;/li&gt;
&lt;li&gt;The Strapi types package (recently separated to reduce build dependencies)&lt;/li&gt;
&lt;li&gt;Design system with Tailwind configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This structure means you have a single source of truth for everything from linting rules to locale configurations.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Next.js 16? The Jack of All Trades
&lt;/h2&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%2F87driw14hqx2lcorrayv.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%2F87driw14hqx2lcorrayv.png" alt="next-16.png" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While the template specifically chose Next.js 16 for the frontend, the decision came from practical experience rather than hype.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Versatility:&lt;/strong&gt; Agencies encounter everything from statically generated marketing sites to dynamic admin dashboards. Next.js handles all these use cases with the same framework.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Team Adoption:&lt;/strong&gt; React's dominance in the job market means easier hiring and onboarding. The entire team could standardize on one framework.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enterprise Deployment:&lt;/strong&gt; Despite internet discourse about Next.js being tied to Vercel, NOTUM has successfully deployed to Azure, AWS S3, Heroku, and other enterprise hosting platforms without significant issues.&lt;/p&gt;

&lt;p&gt;The recent migration to Next.js 16 and React 19 did require switching from Yarn v1 to PNPM (due to multiple React version requirements with Strapi still on React 18), but this change actually improved build sizes and developer experience.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Editting Experience: Where V5 Shines
&lt;/h2&gt;

&lt;p&gt;The template's primary focus is creating an exceptional page editing experience, both for developers and content editors. &lt;/p&gt;

&lt;p&gt;Strapi V5's &lt;a href="https://docs.strapi.io/cms/features/preview" rel="noopener noreferrer"&gt;preview&lt;/a&gt; and &lt;a href="https://docs.strapi.io/cms/features/content-history" rel="noopener noreferrer"&gt;content history&lt;/a&gt; features were game-changers here, solving the long-standing headless CMS problem of not seeing what you're editing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automatic Page Hierarchy
&lt;/h3&gt;

&lt;p&gt;One standout feature is the automatic full-path generation system. Instead of manually managing URL paths, the template uses a parent-child relationship system:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a root page (the homepage)&lt;/li&gt;
&lt;li&gt;Create child pages with just a slug and parent reference&lt;/li&gt;
&lt;li&gt;The system automatically computes full paths recursively&lt;/li&gt;
&lt;li&gt;Change any slug in the hierarchy, and all child paths update automatically&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This isn't a plugin—it's built using Strapi's lifecycle hooks and the DB query builder. The implementation required some SQL magic since lifecycle hooks don't always provide references to current relation versions, but the result is seamless path management.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automatic Redirects
&lt;/h3&gt;

&lt;p&gt;When page paths change, the system automatically generates redirect entries. No more broken links when restructuring your site's information architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  Solving the Population Problem
&lt;/h2&gt;

&lt;p&gt;Anyone who's worked with Strapi at scale knows the populate challenge. With dynamic zones containing dozens (or hundreds) of components, the populate object becomes unwieldy.&lt;/p&gt;

&lt;h3&gt;
  
  
  The URL Length Limit
&lt;/h3&gt;

&lt;p&gt;Here's a problem most tutorials don't mention: when you have many components, your populate query parameters can exceed URL length limits. The frontend simply cannot request everything it needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server-Side Population
&lt;/h3&gt;

&lt;p&gt;NOTUM's solution moves population logic to Strapi itself using document middleware:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The middleware detects specific request patterns (like fetching a single page)&lt;/li&gt;
&lt;li&gt;It checks for a custom &lt;code&gt;middleware_populate&lt;/code&gt; query parameter&lt;/li&gt;
&lt;li&gt;If conditions are met, it replaces the client's populate object with a pre-defined server-side version&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This approach keeps the frontend lean, avoids URL length issues, and provides a security benefit—you explicitly control what data can be populated.&lt;/p&gt;

&lt;p&gt;They originally used the popular "populate deep" plugin but abandoned it due to performance issues at scale.&lt;/p&gt;

&lt;p&gt;You can also accomplish this via route middleware to allow route based population, you can learn more about it &lt;a href="https://strapi.io/blog/route-based-middleware-to-handle-default-population-query-logic" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Developer Experience Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Scripts for Common Tasks
&lt;/h3&gt;

&lt;p&gt;The template includes utility scripts for developer quality of life:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remove all node_modules across the monorepo&lt;/li&gt;
&lt;li&gt;Clear build caches for all applications&lt;/li&gt;
&lt;li&gt;Quickly reset to a clean state when dealing with stale cache issues&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GitHub Integration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Husky for commit message formatting (enforcing company-wide standards)&lt;/li&gt;
&lt;li&gt;GitHub Actions for build verification&lt;/li&gt;
&lt;li&gt;Dependabot configuration for automated dependency updates&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  VS Code Configuration
&lt;/h3&gt;

&lt;p&gt;Recommended extensions and workspace settings come pre-configured, though this area continues to evolve.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-World Deployment
&lt;/h2&gt;

&lt;p&gt;The template ships with Docker support for maximum deployment flexibility. This wasn't added for theoretical completeness—it came from actual enterprise client requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tested Platforms:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Azure (used extensively on multiple projects)&lt;/li&gt;
&lt;li&gt;AWS S3 (for statically generated applications)&lt;/li&gt;
&lt;li&gt;Heroku (for teams preferring simpler deployment)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The containerized approach means you're not locked into any specific hosting provider, a crucial requirement for enterprise clients with existing infrastructure preferences.&lt;/p&gt;

&lt;p&gt;This a super power of Strapi, you can decide where and how you want to deploy it.  My favorite place is &lt;a href="https://strapi.io/cloud" rel="noopener noreferrer"&gt;Strapi Cloud&lt;/a&gt;. But you  can decide which approach works best for you.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons Learned: Strapi V5 Lifecycle Gotchas
&lt;/h2&gt;

&lt;p&gt;NOTUM offered valuable feedback for developers diving deep into Strapi customization:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lifecycle Changes in V5:&lt;/strong&gt; The introduction of content history changed how lifecycles work. Publishing a new version now triggers the create lifecycle, which can cause unexpected behavior if you're not prepared for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Relation Data Availability:&lt;/strong&gt; When making adjustments that don't change relations, those relations won't be present in the lifecycle payload. This is technically correct but not always obvious.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Admin Panel vs API Payloads:&lt;/strong&gt; Calling the same endpoint from the admin panel versus a custom API client produces different lifecycle payloads. This inconsistency can trip up even experienced developers.&lt;/p&gt;

&lt;p&gt;The takeaway isn't that these behaviors are wrong—they often have valid technical reasons—but they're not always well documented. NOTUM recommends approaching lifecycles with caution and always checking actual behavior against assumptions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;note:&lt;/strong&gt; Did you know, you can alway provide feedback and feature request &lt;a href="https://feedback.strapi.io/" rel="noopener noreferrer"&gt;here&lt;/a&gt; or continue the conversation in &lt;a href="https://github.com/strapi/strapi/discussions" rel="noopener noreferrer"&gt;GitHub discussions&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  AI-Assisted Development: The Evolving Workflow
&lt;/h2&gt;

&lt;p&gt;NOTUM is actively exploring AI integration in their development workflow, particularly for documentation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Building an internal knowledge base in Markdown&lt;/li&gt;
&lt;li&gt;Experimenting with AI agents that understand the template's specifics&lt;/li&gt;
&lt;li&gt;Working toward publishable "skills" for AI tools that understand their codebase&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Their philosophy aligns with broader industry observations: there's no one-size-fits-all AI workflow. Each developer should find what makes them productive rather than copying someone else's setup.&lt;/p&gt;




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

&lt;p&gt;The template is designed for quick setup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href=""&gt;Clone the repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Ensure you're using the correct Node version (the repo includes version configuration)&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;pnpm install&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Start Docker (required for the database)&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;pnpm run dev:strapi&lt;/code&gt; to start the backend&lt;/li&gt;
&lt;li&gt;Create an API token in Strapi's admin panel&lt;/li&gt;
&lt;li&gt;Add the token to your Next.js &lt;code&gt;.env.local&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;Run the frontend&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Check out this video on how to set the project up.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion: Why Templates Matter
&lt;/h2&gt;

&lt;p&gt;The difference between a tutorial template and a production template is measured in months of real-world usage. NOTUM's starter represents the accumulated wisdom from dozens of client projects, from startup landing pages to enterprise applications.&lt;/p&gt;

&lt;p&gt;Every time they complete a project—or even mid-project when something important emerges—they backport those learnings into the template. This continuous improvement cycle means you're not just getting a starting point; you're getting a documented history of what works (and what doesn't) in production.&lt;/p&gt;

&lt;p&gt;Whether you use the template directly or study it as a reference for your own architecture, there's value in learning from a team that has shipped—and maintained—this stack at scale.&lt;/p&gt;

&lt;p&gt;You can also checkout out our official Strapi Stater project called &lt;a href="https://strapi.io/blog/getting-started-with-strapi-and-next-js-launchpad-the-official-strapi-demo" rel="noopener noreferrer"&gt;Launchpad&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NOTUM Technologies:&lt;/strong&gt; For companies needing development partners with deep Strapi expertise&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Repository:&lt;/strong&gt; Open-source starter template (link in video description)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strapi Community:&lt;/strong&gt; Join local meetups to connect with other developers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Did you know?&lt;/strong&gt;&lt;br&gt;
Strapi hosts Open Office Hours on Discord Monday through Friday at 12:30 PM CST (running until about 1:30 PM CST). It's a great way to connect, get help, and share your feedback directly with the team.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was created based on a conversation with Dominic from NOTUM Technologies about their Next.js + Strapi starter template.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://strapi.io/blog/building-production-ready-strapi-next-js-16-applications-lessons-from-notum-technologies" rel="noopener noreferrer"&gt;strapi.io&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>strapi</category>
    </item>
    <item>
      <title>Building Native Apps With Expo and React Native</title>
      <dc:creator>Theodore Kelechukwu Onyejiaku</dc:creator>
      <pubDate>Fri, 06 Feb 2026 12:52:43 +0000</pubDate>
      <link>https://dev.to/javascriptar/building-native-apps-with-expo-and-react-native-2ine</link>
      <guid>https://dev.to/javascriptar/building-native-apps-with-expo-and-react-native-2ine</guid>
      <description>&lt;p&gt;&lt;strong&gt;This post is based on our livestream conversation with &lt;a href="https://twitter.com/kadikraman" rel="noopener noreferrer"&gt;Kadi Kraman&lt;/a&gt;, Developer Experience Engineer at &lt;a href="https://docs.expo.dev/" rel="noopener noreferrer"&gt;Expo&lt;/a&gt;.&lt;/strong&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Expo is the Next.js of React Native&lt;/strong&gt; — it handles all the complex native tooling so you can focus on building your app&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Start with Expo Go&lt;/strong&gt; for learning and prototyping; switch to &lt;strong&gt;development builds&lt;/strong&gt; for production (no limitations)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CNG (Continuous Native Generation)&lt;/strong&gt; means you never commit native folders — they're auto-generated and disposable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React Native renders actual native components&lt;/strong&gt;, unlike Flutter which draws its own UI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three styling options&lt;/strong&gt;: Built-in StyleSheet, NativeWind (Tailwind), or Unistyles (best for theming)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EAS services&lt;/strong&gt; handle building, submitting, and updating your apps — no Mac required for iOS builds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You can write server code&lt;/strong&gt; directly in your React Native app with &lt;code&gt;+api.ts&lt;/code&gt; files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Meta officially recommends Expo&lt;/strong&gt; for React Native development&lt;/li&gt;
&lt;/ul&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%2Fyp3xjrtra1p17erakbbo.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%2Fyp3xjrtra1p17erakbbo.png" alt="002-expo.png" width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Choose Expo for Mobile Development?
&lt;/h2&gt;

&lt;p&gt;Think of Expo as the Next.js of React Native. Just as you wouldn't manually configure Webpack and bundlers when building a React web app, Expo eliminates the complexity of native mobile development tooling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Problem Expo Solves&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://reactnative.dev" rel="noopener noreferrer"&gt;React Native&lt;/a&gt; by itself is a toolkit of APIs and a JavaScript runtime, but it doesn't provide a complete framework for building apps. For example, navigation between screens isn't included out of the box. &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%2Flkhop8zwun31ficu5bcf.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%2Flkhop8zwun31ficu5bcf.png" alt="react-native.png" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Without Expo, you'd need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manually configure native build tools (Xcode, Android Studio)&lt;/li&gt;
&lt;li&gt;Research and choose from dozens of libraries for basic functionality&lt;/li&gt;
&lt;li&gt;Manage native iOS and Android folders manually&lt;/li&gt;
&lt;li&gt;Handle complex upgrade paths when React Native versions change&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What Expo Provides&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Expo is a collection of libraries, utilities, bundlers, and a complete ecosystem that makes building React Native apps pleasant and straightforward. Key benefits include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Expo SDK&lt;/strong&gt;: A curated set of native libraries guaranteed to work together (video, audio, haptics, push notifications, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expo Router&lt;/strong&gt;: File-based routing similar to Next.js&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expo Go&lt;/strong&gt;: Test your app instantly on your phone without building native binaries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CNG (Continuous Native Generation)&lt;/strong&gt;: Native folders are generated automatically and can be thrown away and regenerated&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  React Native vs. Flutter: Understanding the Difference
&lt;/h2&gt;

&lt;p&gt;A common question is how React Native compares to Flutter. The key distinction is in rendering: Flutter renders on Skia (a graphics engine), essentially drawing its own UI that hooks into native APIs. React Native, on the other hand, renders actual native components. When you build a button in React Native, it's a real native button, not a simulation. This is why React Native apps are often described as "native apps" — because they truly are.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started: Expo Go vs. Development Builds
&lt;/h2&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%2F463ec3kgawetkqv87stm.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%2F463ec3kgawetkqv87stm.png" alt="expo-go.png" width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Expo offers two approaches for development, each suited to different stages of your project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expo Go: Perfect for Learning and Prototyping&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Expo Go is an app you download from the App Store or Play Store. It contains pre-built native code for the most common libraries, allowing you to start developing immediately without any native build tools. Simply write JavaScript/TypeScript, and your code runs instantly on your device.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Learning React Native, rapid prototyping, hackathons, and getting that first "app on your phone" moment. This instant feedback loop is why many AI-powered app builders (like Replit) use Expo under the hood.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limitations:&lt;/strong&gt; Not all libraries are included. Some Expo SDK libraries (like expo-video) and third-party packages won't work in Expo Go.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Development Builds: Production-Ready Flexibility&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you're ready for production or need libraries not included in Expo Go, you switch to development builds. Instead of using the Expo Go app, you create your own native shell with exactly the libraries you need.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Production apps, custom native code, any library from the React Native ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key insight:&lt;/strong&gt; There are no limitations with development builds. Once you move past Expo Go, you have full access to native code. You can even write custom native modules in Swift or Kotlin using the Expo Modules API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Continuous Native Generation (CNG): The Secret Sauce
&lt;/h2&gt;

&lt;p&gt;One of Expo's most powerful features is CNG (also called "prebuild"). Traditional React Native development requires you to commit and maintain iOS and Android folders, managing native code directly. With CNG:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your native folders are git-ignored&lt;/li&gt;
&lt;li&gt;They're generated automatically from your package.json, app.json, and node_modules&lt;/li&gt;
&lt;li&gt;Upgrades become trivial: delete the native folders, regenerate with new versions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you need to modify native configuration (like editing Info.plist or the app delegate), you create &lt;strong&gt;config plugins&lt;/strong&gt; instead of editing files directly. These plugins run during generation, making your changes repeatable and testable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Styling Your App: Three Approaches
&lt;/h2&gt;

&lt;p&gt;Coming from web development, you might wonder about styling options. React Native offers several approaches:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;StyleSheet (Built-in)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;React Native's built-in styling API. Can be verbose for theming.&lt;/td&gt;
&lt;td&gt;Projects that want zero dependencies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NativeWind&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tailwind CSS for React Native. Familiar utility classes.&lt;/td&gt;
&lt;td&gt;Web developers wanting quick start&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Unistyles&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Like StyleSheet but with better theming support (light/dark mode).&lt;/td&gt;
&lt;td&gt;Apps needing robust theming&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Pro tip:&lt;/strong&gt; If you want your app to look distinctly "native" rather than like a website, study the Apple Human Interface Guidelines and Google Material Design specs. Expo UI (currently in beta) is also exposing more native components like segmented controls and glass buttons.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment: From Code to App Store
&lt;/h2&gt;

&lt;p&gt;Deploying mobile apps is fundamentally different from web deployment. You need to handle code signing (certificates and provisioning profiles) and pass through a manual review process. Expo offers several services to streamline this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EAS Build&lt;/strong&gt;: Cloud-based CI/CD that builds your native apps. Particularly valuable because you need a Mac to build iOS apps — EAS Build provides that infrastructure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EAS Submit&lt;/strong&gt;: Uploads your built apps to the App Store and Play Store. Free to use and essential since Apple requires specific tools (Transporter) to upload apps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EAS Update&lt;/strong&gt;: Push JavaScript updates without going through app store review. Fix bugs instantly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://launch.expo.dev/" rel="noopener noreferrer"&gt;Expo Launch&lt;/a&gt;&lt;/strong&gt;: A new beta product that takes a GitHub repo and handles everything — building, signing, and submission. Currently supports public repos and iOS (Play Store coming soon).&lt;/li&gt;
&lt;/ul&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%2Fpd0c9zzr192sexyz3na6.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%2Fpd0c9zzr192sexyz3na6.png" alt="Screenshot 2026-01-22 at 12.30.28 PM.png" width="800" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing Server Code in Your React Native App
&lt;/h2&gt;

&lt;p&gt;A powerful recent addition is the ability to write API routes directly in your React Native codebase. Create files ending in &lt;code&gt;+api.ts&lt;/code&gt; in your source folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/posts+api.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello from server!&lt;/span&gt;&lt;span class="dl"&gt;"&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;p&gt;Environment variables without the &lt;code&gt;EXPO_PUBLIC_&lt;/code&gt; prefix are only accessible in these server routes, keeping your secrets safe. Deploy with EAS Hosting and you have a full-stack mobile app with backend API routes.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-Assisted Development with Expo
&lt;/h2&gt;

&lt;p&gt;The Expo ecosystem is particularly well-suited for AI-assisted development. With Expo Go's instant feedback loop, tools like Replit and various AI coding assistants can help users go from idea to working app in minutes.&lt;/p&gt;

&lt;p&gt;However, there's an important distinction between "vibe coding" (letting AI write everything) and AI-assisted development. Even when using AI tools, you still own the code and are responsible for testing, iterating, and ensuring quality. AI can give you a starting point, but professional development still requires understanding what the code does.&lt;/p&gt;

&lt;p&gt;Resources like MCPs (Model Context Protocols), cursor rules, and Claude rules allow experts to codify best practices that AI tools can use as a source of truth — resulting in much higher quality generated code than generic training data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Soon: Performance Observability
&lt;/h2&gt;

&lt;p&gt;Expo is developing an observability product for monitoring app performance — think Lighthouse scores but for native apps. You'll be able to track metrics like JavaScript bundle load time, see how different app versions affect performance, and identify slow devices or sessions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Start Checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install Node.js&lt;/strong&gt; and create a new project with &lt;code&gt;npx create-expo-app@latest&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Download Expo Go&lt;/strong&gt; on your phone for instant testing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run &lt;code&gt;npx expo start&lt;/code&gt;&lt;/strong&gt; and scan the QR code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the Expo docs&lt;/strong&gt; (docs.expo.dev) — they have an AI assistant built-in&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check React Native Directory&lt;/strong&gt; (reactnative.directory) for community libraries&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Additional Learning Resources
&lt;/h2&gt;

&lt;p&gt;To continue your learning journey, here are some excellent resources:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Video Resources&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Full Stack React Native Course by Tube Guruji&lt;/strong&gt;: A comprehensive 4+ hour course building a local business directory app from scratch using React Native, Expo, and a full backend. Perfect for hands-on learning. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Written Tutorial&lt;/strong&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%2Folsia0b9o07f0wnqks7l.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%2Folsia0b9o07f0wnqks7l.png" alt="Your paragraph text.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="(https://strapi.io/blog/building-a-react-native-app-with-expo-and-strapi-a-complete-guide)"&gt;Building a React Native App with Expo and Strapi: A Complete Guide&lt;/a&gt;&lt;/strong&gt;: A detailed step-by-step tutorial from the Strapi blog covering project setup, NativeWind styling, data fetching with React Query, and building a complete blog app with infinite scroll. &lt;a href="(https://strapi.io/blog/building-a-react-native-app-with-expo-and-strapi-a-complete-guide)"&gt;Read it here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Expo has transformed React Native development from a complex, native-tooling-heavy process into something that feels almost as simple as web development. For React developers, there's never been a better time to start building mobile apps.&lt;/p&gt;

&lt;p&gt;The key takeaways: Start with Expo Go for learning, graduate to development builds for production, embrace CNG to avoid native folder maintenance, and leverage EAS services for painless deployment. With these tools, you can go from idea to app store in record time.&lt;/p&gt;

&lt;p&gt;Meta officially recommends Expo for React Native development, and it's clear why: the developer experience is exceptional, the ecosystem is mature, and the path from prototype to production is smoother than ever.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Happy building!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Originally published at &lt;a href="https://strapi.io/blog/building-native-apps-with-expo-and-react-native" rel="noopener noreferrer"&gt;strapi.io&lt;/a&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>expo</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Bye 2025, Hello 2026! A year in review</title>
      <dc:creator>Theodore Kelechukwu Onyejiaku</dc:creator>
      <pubDate>Fri, 06 Feb 2026 12:49:08 +0000</pubDate>
      <link>https://dev.to/strapi/bye-2025-hello-2026-a-year-in-review-1a89</link>
      <guid>https://dev.to/strapi/bye-2025-hello-2026-a-year-in-review-1a89</guid>
      <description>&lt;p&gt;We’ve just wrapped up 2025 - and what a year it has been! &lt;br&gt;
As we do every single year, let’s take a moment to celebrate all the achievements of the past 12 months, and share what’s coming next!&lt;/p&gt;

&lt;p&gt;Before we go through the details, to all of you who have used Strapi: thank you! By being part of the community, you have contributed to elevating the Strapi open-source project to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;70K+ stars on GitHub (&lt;a href="https://github.com/strapi/strapi/stargazers" rel="noopener noreferrer"&gt;add yours!&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;36K commits&lt;/li&gt;
&lt;li&gt;9.4K pull requests merged&lt;/li&gt;
&lt;li&gt;Thousands of customers, including Adidas, Airbus, Amazon, Cisco, Tesco, Toyota, PostHog, and CodeRabbit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These milestones are especially meaningful, as &lt;a href="https://strapi.io/blog/str-happy-10th-birthday-strapi" rel="noopener noreferrer"&gt;Strapi turned 10&lt;/a&gt; this year!&lt;/p&gt;

&lt;p&gt;12 months ago, &lt;a href="https://strapi.io/blog/bye-2024-hello-2025-a-year-in-review" rel="noopener noreferrer"&gt;we shared&lt;/a&gt; that we would focus on the Side Editor, the New Cloud Plan, the JavaScript/TypeScript SDK, the AI tools, and the first in-person StrapiConf. And that is exactly what we delivered!&lt;/p&gt;

&lt;h2&gt;
  
  
  Product improvements
&lt;/h2&gt;

&lt;p&gt;More than ever before, the Strapi community shipped.&lt;/p&gt;

&lt;h3&gt;
  
  
  Strapi AI
&lt;/h3&gt;

&lt;p&gt;AI is changing everyone’s job. You have to disrupt yourself, and so do we. Content Management Systems sit at the intersection of Developers and Marketers, who both have to reinvent their jobs with code generation (vibe coding and AI coding) and content creation (gen AI). The way &lt;a href="https://www.linkedin.com/feed/update/urn:li:activity:7406368766550237184/" rel="noopener noreferrer"&gt;CMSs are used continues to evolve at an incredible pace&lt;/a&gt;. What an exciting time to be building!&lt;/p&gt;

&lt;p&gt;This is why we invested heavily in AI. In 2025, we unveiled a suite of strategic product enhancements designed to accelerate both developer workflows and content editor experiences.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI Content-Type Builder&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It all started with the AI Content-Type Builder that we announced at StrapiConf (see below). With the brand new AI Content-Type Builder, you can generate content types and components from natural language prompts or uploaded frontend apps/Figma designs. This eliminates redundant tasks, saving days of development time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI Media Library&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Creating alt text and captions is another repetitive -and often overlooked- task. The AI Media Library handles this automatically whenever you upload an asset.&lt;/p&gt;


  
  Your browser does not support the video tag.


&lt;p&gt;&lt;strong&gt;AI Translations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you manage a website in multiple languages, translating content can be a lengthy, costly, and error-prone process. The AI Translations feature has you covered. When enabled, it automatically translates your content into all the locales you selected. This marks an important step in Strapi’s evolution toward an agentic CMS. Merci beaucoup!&lt;/p&gt;


  
  Your browser does not support the video tag.


&lt;p&gt;These are the first steps into the Strapi AI journey. Stay tuned for more!&lt;/p&gt;

&lt;h3&gt;
  
  
  Editing Experience
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Live Preview&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Headless architecture is a great separation of concerns between the frontend and the CMS. However, this separation often came at the cost of the editing experience. Non-technical people need - and deserve - to edit websites and apps in an intuitive and smooth manner. Combined with Strapi 5's Draft &amp;amp; Publish capabilities, Live Preview addresses this challenge.&lt;/p&gt;


  
  Your browser does not support the video tag.


&lt;p&gt;&lt;strong&gt;Responsive&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Editing content for small screens has been a community request for several years. We must admit this comes later than expected, but Strapi is becoming responsive. We've released the first improvements and will keep rolling out additional updates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conditional Fields
&lt;/h3&gt;

&lt;p&gt;At Strapi, we’ve always prioritized giving developers and content teams the flexibility to organize content as they see fit. So we introduced &lt;strong&gt;Conditional Fields&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;With Conditional Fields, you can dynamically show or hide fields based on specific criteria like dropdown selections or checkbox values. This helps editors focus on the right fields at the right time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Developer Experience
&lt;/h2&gt;

&lt;p&gt;Strapi was created by developers, for developers. In 2025, we shipped a significant number of Developer Experience improvements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strapi Client Library&lt;/strong&gt;: The brand new SDK allows you to easily request your Strapi content from your favorite frontend framework.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAPI support&lt;/strong&gt;: We’ve heard many users requesting the ability to benefit from typing when using the Strapi Client Library in a frontend project (Next.js, Tanstack, Astro, Nuxt.js, etc.). OpenAPI support will allow us to add this to the Strapi Client Library.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Homepage widgets&lt;/strong&gt;: From now on, it is possible to display and create widgets on the Strapi homepage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP prototype&lt;/strong&gt;: The first MCP prototype was shipped. This will be a massive enabler in making Strapi fully agentic.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Integrations
&lt;/h2&gt;

&lt;p&gt;A Headless CMS is often used in conjunction with other services, making it the foundation of a composable architecture. In 2025,  in close collaboration with our partner &lt;a href="https://strapi.io/partners/VirtusLab" rel="noopener noreferrer"&gt;VirtusLab&lt;/a&gt;, we launched official &lt;a href="https://strapi.io/blog/official-integrations-and-homepage-widget-api" rel="noopener noreferrer"&gt;integrations for Shopify, BigCommerce, and Cloudinary&lt;/a&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%2Fft2chia4vik5dbk0dr8e.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%2Fft2chia4vik5dbk0dr8e.png" alt="strapi official integrations.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Strapi Cloud
&lt;/h3&gt;

&lt;p&gt;24 months after its launch, Strapi Cloud has become the leading platform for hosting Strapi projects, as it comes with everything you need: Node.js hosting, a Postgres database, asset storage, a CDN, GitHub/GitLab integrations, and more.&lt;/p&gt;

&lt;p&gt;Until 2025, the most affordable plan was the Pro plan, at $99/month. Many of you have shared that this price was too high for small projects. This is why we introduced the &lt;strong&gt;Essential Plan&lt;/strong&gt;, for $15/month (when billed annually). We even went a step further by launching a Free Plan, making Strapi Cloud accessible to everyone.&lt;/p&gt;

&lt;p&gt;Based on your feedback, we released a bunch of other improvements, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ownership transfer&lt;/strong&gt;: Give full access to a Strapi Cloud to anyone (ideally for agencies with clients, and vice versa).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data migration&lt;/strong&gt;: Easily migrate content from one environment to another.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Downloadable backups&lt;/strong&gt;: Save your content and store it anywhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Community milestones
&lt;/h2&gt;

&lt;p&gt;Strapi is so much more than an open-source project; it is a vibrant community of contributors, users, and partners who build faster, together.&lt;/p&gt;

&lt;h3&gt;
  
  
  StrapiConf
&lt;/h3&gt;

&lt;p&gt;After organizing 3 online StrapiConf, we organized the first in-person StrapiConf on May 13, 2025. After years of chats on the &lt;a href="http://discord.strapi.io/" rel="noopener noreferrer"&gt;Strapi Community Discord&lt;/a&gt;, it was fantastic to meet so many of you face-to-face!&lt;/p&gt;

&lt;p&gt;Community members presented incredible plugins. Large organizations, including &lt;a href="https://www.youtube.com/watch?v=pjJNnW0UmgA&amp;amp;pp=2AYO" rel="noopener noreferrer"&gt;Adidas&lt;/a&gt; and &lt;a href="https://www.youtube.com/watch?v=NkkM2fNKCV8" rel="noopener noreferrer"&gt;Airbus&lt;/a&gt;, benefit from the unique customization capabilities that Strapi offers. We had a blast!&lt;/p&gt;

&lt;p&gt;Thank you again to our &lt;a href="https://conf.strapi.io/#sponsors" rel="noopener noreferrer"&gt;amazing sponsors&lt;/a&gt; for empowering the Strapi ecosystem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Community Calls and Events
&lt;/h3&gt;

&lt;p&gt;We had some amazing calls with the community, one of which was a full-on conversation with &lt;a href="https://www.linkedin.com/in/kentcdodds" rel="noopener noreferrer"&gt;Kent C. Dodds&lt;/a&gt; about the future of the web, AI agents, and why Model Context Protocol (MCP) might be as big a shift as the early days of the internet.&lt;/p&gt;

&lt;p&gt;Here are some other talks and events you may have missed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://youtu.be/k4oUvMWpzBk" rel="noopener noreferrer"&gt;Strapi Community Call: Strapi AI, Media Library AI, Plugin Generators (with Boaz) &amp;amp;  Q&amp;amp;A&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtube.com/live/TbqChjpsTpo" rel="noopener noreferrer"&gt;Community Call January 2025: What's New and AMA&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/gcDVX_pyrYc" rel="noopener noreferrer"&gt;Strapi July Community Call 2025&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To join live streams &amp;amp; online meetings on the latest updates &amp;amp; discussions about Strapi, visit our events page: &lt;a href="https://strapi.io/events" rel="noopener noreferrer"&gt;https://strapi.io/events&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Tutorials
&lt;/h3&gt;

&lt;p&gt;Among many amazing tutorials, we created a full tutorial on “&lt;a href="https://www.youtube.com/watch?v=Q-cPtlYG1cY" rel="noopener noreferrer"&gt;Strapi 5 and Next.js 15 Full Stack Project Course&lt;/a&gt;”, which shows you how to build a fully functional summer camp website from start to finish, through hands-on practice.&lt;/p&gt;

&lt;p&gt;Other great tutorials from the community that you may want to see include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://youtu.be/t1iUuap7vhw?si=aMslVieJJXaG3za1" rel="noopener noreferrer"&gt;Strapi 5 Crash Course 2025 Full Tutorial For Beginners&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/pW2Pso4fw5g?si=6kcDogq2IRs0EWRP" rel="noopener noreferrer"&gt;&lt;strong&gt;React Native Project | Build Full Stack React Native App with Expo, &amp;amp; Strapi | AI Recipe Generator&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/fO3D8lNs10c?si=0o_Vlq2xdieEqM0-" rel="noopener noreferrer"&gt;Build Your Own MASTERCLASS clone in React Native&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=VosHdPQr6nI" rel="noopener noreferrer"&gt;Next.js 16 + Strapi 5: Caché components, autenticación y despliegue gratis a producción&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To see more tutorials, visit the &lt;a href="https://strapi.io/blog" rel="noopener noreferrer"&gt;Strapi Blog&lt;/a&gt; or our &lt;a href="https://www.youtube.com/strapi" rel="noopener noreferrer"&gt;YouTube channel&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Marketplace
&lt;/h3&gt;

&lt;p&gt;We introduced several improvements to the Strapi Marketplace:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Plugins &amp;amp; Providers are now merged into one overview&lt;/li&gt;
&lt;li&gt;The overview is sorted by the number of NPM downloads by default&lt;/li&gt;
&lt;li&gt;The home page now directly shows the overview&lt;/li&gt;
&lt;li&gt;Outdated plugins for Strapi v4 have been removed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you to the top plugin creators: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mateusz Ziarko - VirtusLab&lt;/li&gt;
&lt;li&gt;Scherwan Al-Ahmad - MagicDX&lt;/li&gt;
&lt;li&gt;Kevin Vugts - Headlockr&lt;/li&gt;
&lt;li&gt;Tim Schipper - PluginPal&lt;/li&gt;
&lt;li&gt;Nikita Shenderov - CKEditor plugin&lt;/li&gt;
&lt;li&gt;Laurent Cazanove - Meilisearch&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Partner Program
&lt;/h3&gt;

&lt;p&gt;The Partner Program continues to become increasingly beneficial for both enterprises seeking trusted implementers and agencies expanding their client base. &lt;/p&gt;

&lt;p&gt;Special shout-out to Notum, QBurst, Alloy, TenTwenty, and Thrillworks for becoming Enterprise Partners, totaling 5 Enterprise-level partners added in 1 year.&lt;/p&gt;

&lt;p&gt;This year, we launched monthly live Partner Enablement sessions to keep our partners informed on Strapi best practices, product updates, and sales alignment.&lt;/p&gt;

&lt;p&gt;Partners have consistently shared valuable feedback with our product and engineering teams. They've helped identify bugs, feature gaps, and opportunities to price the product more competitively. Thank you for that!&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking ahead to 2026
&lt;/h2&gt;

&lt;p&gt;As you have seen in this article, we have shipped numerous new features, just as we have for the past few years. This is great!&lt;/p&gt;

&lt;p&gt;Over the past few months, you all have shared your opinions through the Community Survey and the &lt;a href="https://github.com/strapi/strapi/issues/24956" rel="noopener noreferrer"&gt;pain point GitHub issue&lt;/a&gt;. A few improvement requests have been around for a long time on the &lt;a href="https://feedback.strapi.io/" rel="noopener noreferrer"&gt;Feedback page&lt;/a&gt;. This is largely because we needed to build a solid company to make the open-source project sustainable and navigate through &lt;a href="https://www.youtube.com/watch?v=AfVzyKl_-kw" rel="noopener noreferrer"&gt;2 different business models&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We’ve heard you loud and clear. Although adding features is exciting, it is time to improve some of the foundations. That's why 2026 will be dedicated to User Experience. And it all starts with Quality - fixing bugs, improving data migration, and strengthening the foundations - while also encompassing both the Developer Experience (MCP, typing in the frontend) and the Editing Experience (Media Library enhancements, etc.). Regarding Strapi Cloud, we are migrating to a new infrastructure that will significantly enhance performance, but also allow us to provide you with direct database access and improved runtime logs.&lt;/p&gt;

&lt;p&gt;We also plan to continue collaborating as a Strapi community, including creating plugins, contributing to the core, and hosting events.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;A Heartfelt Thank You&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Thank you so much for being part of the Strapi community. We are 10 years into this journey, and it feels like we are just getting started.&lt;/p&gt;

&lt;p&gt;Str’happy New Year!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://strapi.io/blog/bye-2025-hello-2026-a-year-in-review" rel="noopener noreferrer"&gt;strapi.io&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>community</category>
      <category>strapi</category>
    </item>
    <item>
      <title>Building a React Native App with Expo and Strapi: A Complete Guide</title>
      <dc:creator>Theodore Kelechukwu Onyejiaku</dc:creator>
      <pubDate>Fri, 06 Feb 2026 12:45:51 +0000</pubDate>
      <link>https://dev.to/strapi/building-a-react-native-app-with-expo-and-strapi-a-complete-guide-5fma</link>
      <guid>https://dev.to/strapi/building-a-react-native-app-with-expo-and-strapi-a-complete-guide-5fma</guid>
      <description>&lt;p&gt;&lt;strong&gt;This post is based on our livestream conversation with &lt;a href="https://twitter.com/kadikraman" rel="noopener noreferrer"&gt;Kadi Kraman&lt;/a&gt;, Developer Experience Engineer at &lt;a href="https://docs.expo.dev/" rel="noopener noreferrer"&gt;Expo&lt;/a&gt;.&lt;/strong&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Expo is the Next.js of React Native&lt;/strong&gt; — it handles all the complex native tooling so you can focus on building your app&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Start with Expo Go&lt;/strong&gt; for learning and prototyping; switch to &lt;strong&gt;development builds&lt;/strong&gt; for production (no limitations)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CNG (Continuous Native Generation)&lt;/strong&gt; means you never commit native folders — they're auto-generated and disposable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React Native renders actual native components&lt;/strong&gt;, unlike Flutter which draws its own UI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three styling options&lt;/strong&gt;: Built-in StyleSheet, NativeWind (Tailwind), or Unistyles (best for theming)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EAS services&lt;/strong&gt; handle building, submitting, and updating your apps — no Mac required for iOS builds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You can write server code&lt;/strong&gt; directly in your React Native app with &lt;code&gt;+api.ts&lt;/code&gt; files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Meta officially recommends Expo&lt;/strong&gt; for React Native development&lt;/li&gt;
&lt;/ul&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%2Fyp3xjrtra1p17erakbbo.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%2Fyp3xjrtra1p17erakbbo.png" alt="002-expo.png" width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Choose Expo for Mobile Development?
&lt;/h2&gt;

&lt;p&gt;Think of Expo as the Next.js of React Native. Just as you wouldn't manually configure Webpack and bundlers when building a React web app, Expo eliminates the complexity of native mobile development tooling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Problem Expo Solves&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://reactnative.dev" rel="noopener noreferrer"&gt;React Native&lt;/a&gt; by itself is a toolkit of APIs and a JavaScript runtime, but it doesn't provide a complete framework for building apps. For example, navigation between screens isn't included out of the box. &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%2Flkhop8zwun31ficu5bcf.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%2Flkhop8zwun31ficu5bcf.png" alt="react-native.png" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Without Expo, you'd need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manually configure native build tools (Xcode, Android Studio)&lt;/li&gt;
&lt;li&gt;Research and choose from dozens of libraries for basic functionality&lt;/li&gt;
&lt;li&gt;Manage native iOS and Android folders manually&lt;/li&gt;
&lt;li&gt;Handle complex upgrade paths when React Native versions change&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What Expo Provides&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Expo is a collection of libraries, utilities, bundlers, and a complete ecosystem that makes building React Native apps pleasant and straightforward. Key benefits include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Expo SDK&lt;/strong&gt;: A curated set of native libraries guaranteed to work together (video, audio, haptics, push notifications, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expo Router&lt;/strong&gt;: File-based routing similar to Next.js&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expo Go&lt;/strong&gt;: Test your app instantly on your phone without building native binaries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CNG (Continuous Native Generation)&lt;/strong&gt;: Native folders are generated automatically and can be thrown away and regenerated&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  React Native vs. Flutter: Understanding the Difference
&lt;/h2&gt;

&lt;p&gt;A common question is how React Native compares to Flutter. The key distinction is in rendering: Flutter renders on Skia (a graphics engine), essentially drawing its own UI that hooks into native APIs. React Native, on the other hand, renders actual native components. When you build a button in React Native, it's a real native button, not a simulation. This is why React Native apps are often described as "native apps" — because they truly are.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started: Expo Go vs. Development Builds
&lt;/h2&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%2F463ec3kgawetkqv87stm.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%2F463ec3kgawetkqv87stm.png" alt="expo-go.png" width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Expo offers two approaches for development, each suited to different stages of your project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expo Go: Perfect for Learning and Prototyping&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Expo Go is an app you download from the App Store or Play Store. It contains pre-built native code for the most common libraries, allowing you to start developing immediately without any native build tools. Simply write JavaScript/TypeScript, and your code runs instantly on your device.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Learning React Native, rapid prototyping, hackathons, and getting that first "app on your phone" moment. This instant feedback loop is why many AI-powered app builders (like Replit) use Expo under the hood.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limitations:&lt;/strong&gt; Not all libraries are included. Some Expo SDK libraries (like expo-video) and third-party packages won't work in Expo Go.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Development Builds: Production-Ready Flexibility&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you're ready for production or need libraries not included in Expo Go, you switch to development builds. Instead of using the Expo Go app, you create your own native shell with exactly the libraries you need.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Production apps, custom native code, any library from the React Native ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key insight:&lt;/strong&gt; There are no limitations with development builds. Once you move past Expo Go, you have full access to native code. You can even write custom native modules in Swift or Kotlin using the Expo Modules API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Continuous Native Generation (CNG): The Secret Sauce
&lt;/h2&gt;

&lt;p&gt;One of Expo's most powerful features is CNG (also called "prebuild"). Traditional React Native development requires you to commit and maintain iOS and Android folders, managing native code directly. With CNG:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your native folders are git-ignored&lt;/li&gt;
&lt;li&gt;They're generated automatically from your package.json, app.json, and node_modules&lt;/li&gt;
&lt;li&gt;Upgrades become trivial: delete the native folders, regenerate with new versions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you need to modify native configuration (like editing Info.plist or the app delegate), you create &lt;strong&gt;config plugins&lt;/strong&gt; instead of editing files directly. These plugins run during generation, making your changes repeatable and testable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Styling Your App: Three Approaches
&lt;/h2&gt;

&lt;p&gt;Coming from web development, you might wonder about styling options. React Native offers several approaches:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;StyleSheet (Built-in)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;React Native's built-in styling API. Can be verbose for theming.&lt;/td&gt;
&lt;td&gt;Projects that want zero dependencies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NativeWind&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tailwind CSS for React Native. Familiar utility classes.&lt;/td&gt;
&lt;td&gt;Web developers wanting quick start&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Unistyles&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Like StyleSheet but with better theming support (light/dark mode).&lt;/td&gt;
&lt;td&gt;Apps needing robust theming&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Pro tip:&lt;/strong&gt; If you want your app to look distinctly "native" rather than like a website, study the Apple Human Interface Guidelines and Google Material Design specs. Expo UI (currently in beta) is also exposing more native components like segmented controls and glass buttons.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment: From Code to App Store
&lt;/h2&gt;

&lt;p&gt;Deploying mobile apps is fundamentally different from web deployment. You need to handle code signing (certificates and provisioning profiles) and pass through a manual review process. Expo offers several services to streamline this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EAS Build&lt;/strong&gt;: Cloud-based CI/CD that builds your native apps. Particularly valuable because you need a Mac to build iOS apps — EAS Build provides that infrastructure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EAS Submit&lt;/strong&gt;: Uploads your built apps to the App Store and Play Store. Free to use and essential since Apple requires specific tools (Transporter) to upload apps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EAS Update&lt;/strong&gt;: Push JavaScript updates without going through app store review. Fix bugs instantly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://launch.expo.dev/" rel="noopener noreferrer"&gt;Expo Launch&lt;/a&gt;&lt;/strong&gt;: A new beta product that takes a GitHub repo and handles everything — building, signing, and submission. Currently supports public repos and iOS (Play Store coming soon).&lt;/li&gt;
&lt;/ul&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%2Fpd0c9zzr192sexyz3na6.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%2Fpd0c9zzr192sexyz3na6.png" alt="Screenshot 2026-01-22 at 12.30.28 PM.png" width="800" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing Server Code in Your React Native App
&lt;/h2&gt;

&lt;p&gt;A powerful recent addition is the ability to write API routes directly in your React Native codebase. Create files ending in &lt;code&gt;+api.ts&lt;/code&gt; in your source folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/posts+api.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello from server!&lt;/span&gt;&lt;span class="dl"&gt;"&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;p&gt;Environment variables without the &lt;code&gt;EXPO_PUBLIC_&lt;/code&gt; prefix are only accessible in these server routes, keeping your secrets safe. Deploy with EAS Hosting and you have a full-stack mobile app with backend API routes.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-Assisted Development with Expo
&lt;/h2&gt;

&lt;p&gt;The Expo ecosystem is particularly well-suited for AI-assisted development. With Expo Go's instant feedback loop, tools like Replit and various AI coding assistants can help users go from idea to working app in minutes.&lt;/p&gt;

&lt;p&gt;However, there's an important distinction between "vibe coding" (letting AI write everything) and AI-assisted development. Even when using AI tools, you still own the code and are responsible for testing, iterating, and ensuring quality. AI can give you a starting point, but professional development still requires understanding what the code does.&lt;/p&gt;

&lt;p&gt;Resources like MCPs (Model Context Protocols), cursor rules, and Claude rules allow experts to codify best practices that AI tools can use as a source of truth — resulting in much higher quality generated code than generic training data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Soon: Performance Observability
&lt;/h2&gt;

&lt;p&gt;Expo is developing an observability product for monitoring app performance — think Lighthouse scores but for native apps. You'll be able to track metrics like JavaScript bundle load time, see how different app versions affect performance, and identify slow devices or sessions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Start Checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install Node.js&lt;/strong&gt; and create a new project with &lt;code&gt;npx create-expo-app@latest&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Download Expo Go&lt;/strong&gt; on your phone for instant testing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run &lt;code&gt;npx expo start&lt;/code&gt;&lt;/strong&gt; and scan the QR code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the Expo docs&lt;/strong&gt; (docs.expo.dev) — they have an AI assistant built-in&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check React Native Directory&lt;/strong&gt; (reactnative.directory) for community libraries&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Additional Learning Resources
&lt;/h2&gt;

&lt;p&gt;To continue your learning journey, here are some excellent resources:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Video Resources&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Full Stack React Native Course by Tube Guruji&lt;/strong&gt;: A comprehensive 4+ hour course building a local business directory app from scratch using React Native, Expo, and a full backend. Perfect for hands-on learning. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Written Tutorial&lt;/strong&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%2Folsia0b9o07f0wnqks7l.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%2Folsia0b9o07f0wnqks7l.png" alt="Your paragraph text.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="(https://strapi.io/blog/building-a-react-native-app-with-expo-and-strapi-a-complete-guide)"&gt;Building a React Native App with Expo and Strapi: A Complete Guide&lt;/a&gt;&lt;/strong&gt;: A detailed step-by-step tutorial from the Strapi blog covering project setup, NativeWind styling, data fetching with React Query, and building a complete blog app with infinite scroll. &lt;a href="(https://strapi.io/blog/building-a-react-native-app-with-expo-and-strapi-a-complete-guide)"&gt;Read it here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Expo has transformed React Native development from a complex, native-tooling-heavy process into something that feels almost as simple as web development. For React developers, there's never been a better time to start building mobile apps.&lt;/p&gt;

&lt;p&gt;The key takeaways: Start with Expo Go for learning, graduate to development builds for production, embrace CNG to avoid native folder maintenance, and leverage EAS services for painless deployment. With these tools, you can go from idea to app store in record time.&lt;/p&gt;

&lt;p&gt;Meta officially recommends Expo for React Native development, and it's clear why: the developer experience is exceptional, the ecosystem is mature, and the path from prototype to production is smoother than ever.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Happy building!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://strapi.io/blog/building-a-react-native-app-with-expo-and-strapi-a-complete-guide" rel="noopener noreferrer"&gt;strapi.io&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>react</category>
      <category>expo</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Introducing Yearly Plans and New Limits for Strapi Cloud</title>
      <dc:creator>Theodore Kelechukwu Onyejiaku</dc:creator>
      <pubDate>Fri, 06 Feb 2026 12:34:03 +0000</pubDate>
      <link>https://dev.to/strapi/introducing-yearly-plans-and-new-limits-for-strapi-cloud-165l</link>
      <guid>https://dev.to/strapi/introducing-yearly-plans-and-new-limits-for-strapi-cloud-165l</guid>
      <description>&lt;p&gt;Back in March, we introduced a &lt;a href="https://strapi.io/blog/lower-prices-and-greater-flexibility-with-improved-strapi-cloud-pricing" rel="noopener noreferrer"&gt;new pricing model, making Strapi Cloud more accessible&lt;/a&gt;. The intention was simple: reduce friction for builders. We simplified Strapi Cloud pricing by offering “hosting-only plans” (without CMS features), increasing included storage, and removing CMS seat and entry limits. The result was a pricing model that grows with your business, where you only pay for what you need when you need it. This approach makes it easier for you to scale, allowing you to focus less on infrastructure management and more on building products that matter. &lt;/p&gt;

&lt;p&gt;Today, we’re taking the next step in that direction. We're introducing &lt;strong&gt;annual billing with 20% savings&lt;/strong&gt;, alongside refined usage limits that ensure each plan serves its intended purpose. &lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;20% discount for yearly billing compared to monthly billing&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refined usage limits:&lt;/strong&gt; Updated API request limits for Free and Essential plans&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monthly quota resets:&lt;/strong&gt; API requests and bandwidth renew monthly, even on yearly plans&lt;/li&gt;
&lt;li&gt;Explore the &lt;a href="https://strapi.io/pricing" rel="noopener noreferrer"&gt;&lt;strong&gt;Strapi Cloud Pricing page&lt;/strong&gt;&lt;/a&gt; and &lt;a href="https://support.strapi.io/articles/3581379360-understanding-strapi-cloud-usage-and-billing" rel="noopener noreferrer"&gt;&lt;strong&gt;Cloud billing &amp;amp; usage docs&lt;/strong&gt;&lt;/a&gt; for complete details&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What’s new and what it means for you
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Introducing Yearly Pricing&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We heard from many customers that they prefer annual billing for simplified budgeting and reduced administrative overhead. One annual invoice means less time spent processing payments for both developers and accounting teams.&lt;/p&gt;

&lt;p&gt;You can now choose &lt;strong&gt;yearly billing&lt;/strong&gt; on all paid Strapi Cloud plans and get a &lt;strong&gt;20% discount&lt;/strong&gt; compared to monthly billing, with one annual invoice instead of twelve. &lt;/p&gt;

&lt;p&gt;Yearly plans offer the best value for teams that are ready to commit and plan ahead.  &lt;strong&gt;Monthly billing remains available&lt;/strong&gt; for teams that require maximum flexibility and the ability to cancel at any time, albeit at a higher monthly rate. This reflects the trade-off between flexibility and long-term savings, allowing you to choose what best suits your situation.&lt;/p&gt;

&lt;p&gt;Existing monthly customers will continue on their current plans without any changes to their pricing. You're grandfathered in at your existing rates. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monthly resets still apply on yearly plans too&lt;/strong&gt;, regardless of how you’re billed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API requests reset monthly&lt;/li&gt;
&lt;li&gt;Bandwidth resets monthly&lt;/li&gt;
&lt;li&gt;Asset storage remains an ongoing limit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're currently on a monthly plan, don't worry, you'll keep your current pricing. Your rates won't change, and you'll continue with the same limits you have today. &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Updated API request limits&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We’ve refined API request limits on two plans to better reflect how they’re typically used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free plan:&lt;/strong&gt; 2,500 API requests/month (previously 10,000)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Essential plan:&lt;/strong&gt; 50,000 API requests/month (previously 100,000) – &lt;em&gt;applies to new subscriptions only&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;strong&gt;Free plan&lt;/strong&gt; remains ideal for personal projects, learning, and early prototypes. These refined limits enable you to build in a stable environment, knowing exactly when your project is ready to transition from proof of concept to production. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Existing Essential customers are protected.&lt;/strong&gt; If you’re already on the &lt;strong&gt;Essential plan&lt;/strong&gt;, nothing changes. You’ll keep your existing &lt;strong&gt;100,000 API requests/month&lt;/strong&gt; limit. No action required. No disruption. We believe trust is built by honoring existing commitments and protecting the way you’re already building.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we changed the limits
&lt;/h2&gt;

&lt;p&gt;To be clear and honest, &lt;strong&gt;lower limits aren’t a direct benefit to individual users&lt;/strong&gt;. These updates help keep Strapi Cloud reliable, sustainable, and aligned with how each plan is intended to be used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free plan:&lt;/strong&gt; lower limits ensure stability and usability for everyone experimenting or learning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Platform sustainability:&lt;/strong&gt; helps maintain performance, prevent abuse, and fund continued investment in new capabilities.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your project outgrows a plan, that’s a signal it’s ready for the next stage.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s next for Strapi Cloud
&lt;/h2&gt;

&lt;p&gt;Alongside these pricing updates, we're continuously building new capabilities across all paid Strapi Cloud plans designed to give you more visibility, control, and speed as your projects grow. &lt;/p&gt;

&lt;p&gt;This includes enhanced logging, monitoring, and other improvements focused on improving the developer experience. These initiatives are shaping the &lt;strong&gt;2026 roadmap&lt;/strong&gt;, and we'll share more details as they become available.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;FAQ&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Will yearly plans be available for the Growth (CMS) plan?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yearly billing currently applies only to &lt;strong&gt;Strapi Cloud hosting plans&lt;/strong&gt; (Essential, Pro, Scale). CMS feature licenses (Growth) remain separate for now and are available on a monthly basis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I stay on the Free plan and pay overages?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No, overages don't apply to the Free plan. If you need additional resources, upgrading to a paid plan starting at just $18/month (or $15/month with yearly billing) gives you significantly more capacity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do monthly quotas reset monthly, even on yearly plans?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Yes. API requests and bandwidth renewals occur on a monthly basis on yearly plans. Unlike Asset storage, which is an ongoing limit. For example, an Essential customer with 100,000 API requests/month makes 125,000 requests in a given month. They exceeded their quota by 25,000 API requests and would be charged an additional $1.50. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I switch from monthly to yearly billing and vice versa?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes, you can adjust your billing frequency in your dashboard at any point in time. However, the change will take effect at the end of your current billing cycle. For yearly plans, this means the switch will occur after your 12-month commitment has been completed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is Strapi Cloud better than managing my own servers?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In most cases, yes, it greatly reduces operational overhead and lets you build faster. If you require full infrastructure control or want to keep both your frontend and backend on the same infrastructure, self-hosting remains an option.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens to existing Essential customers with the new limits?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Current Essential customers keep their existing 100,000 API requests/month limit and won't be affected by these changes. The new 50,000 API requests/month limit only applies to new Essential subscriptions created after &lt;strong&gt;December 22nd, 2025&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://strapi.io/blog/introducing-yearly-plans-and-new-limits-for-strapi-cloud" rel="noopener noreferrer"&gt;strapi.io&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>strapi</category>
      <category>cloud</category>
      <category>strapicloud</category>
    </item>
  </channel>
</rss>
