Try Live
Add Docs
Rankings
Pricing
Docs
Install
Install
Docs
Pricing
More...
More...
Try Live
Rankings
Enterprise
Create API Key
Add Docs
Craft CMS
https://github.com/craftcms/cms
Admin
Craft CMS is a flexible and user-friendly content management system for creating custom digital
...
Tokens:
13,842
Snippets:
80
Trust Score:
9.9
Update:
7 months ago
Context
Skills
Chat
Benchmark
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Craft CMS ## Introduction Craft CMS is a flexible, self-hosted PHP content management system built on the Yii 2 framework. It provides a clean-slate approach to content modeling without making assumptions about the structure of your content. The system features an intuitive control panel for content creators, a powerful Twig-based templating engine, and an auto-generated GraphQL API for headless applications. Craft's architecture supports multi-site deployments, custom field types, and extensibility through a robust plugin ecosystem. The core system is organized around elements (entries, assets, users, categories, tags), each with customizable field layouts. Craft uses a service-oriented architecture where business logic resides in service classes accessed via `Craft::$app`, controllers handle HTTP requests, and the database layer uses Yii's ActiveRecord pattern. The framework includes over 47 built-in services managing everything from content operations to authentication, search, and GraphQL schema generation. --- ## Core APIs and Functions ### Query and Retrieve Entries Query entries from a section with filtering, ordering, and eager loading support. ```php // Query published blog entries with author information $entries = \craft\elements\Entry::find() ->section('blog') ->type('article') ->status('live') ->orderBy('postDate DESC') ->with(['author']) ->limit(10) ->all(); // Access entry data and custom fields foreach ($entries as $entry) { echo $entry->title; echo $entry->slug; echo $entry->postDate->format('Y-m-d H:i:s'); echo $entry->author->email; // Custom fields via magic properties echo $entry->customTextField; echo $entry->customImageField->one()->url; } // Query with custom field filtering $filtered = \craft\elements\Entry::find() ->section('products') ->customPrice(['>', 100]) ->customCategory('electronics') ->all(); ``` ### Create and Save Entry Elements Create new entries programmatically with field values and validation. ```php use craft\elements\Entry; use Craft; // Create a new entry $entry = new Entry(); $entry->sectionId = 5; $entry->typeId = 3; $entry->siteId = 1; $entry->title = 'My New Article'; $entry->slug = 'my-new-article'; // Set author $entry->authorId = Craft::$app->getUser()->getId(); // Set custom field values $entry->setFieldValue('bodyContent', '<p>Article body text here</p>'); $entry->setFieldValue('featuredImage', [42]); // Asset IDs $entry->setFieldValue('tags', [10, 11, 12]); // Tag IDs // Set dates $entry->postDate = new \DateTime('2025-01-15 10:00:00'); $entry->expiryDate = new \DateTime('2025-12-31 23:59:59'); // Save the entry if (Craft::$app->elements->saveElement($entry)) { echo "Entry saved successfully with ID: {$entry->id}"; } else { echo "Failed to save entry:"; print_r($entry->getErrors()); } // Alternative: Save with validation disabled (use with caution) Craft::$app->elements->saveElement($entry, false); ``` ### GraphQL API Query Endpoint Execute GraphQL queries via HTTP POST to retrieve content. ```bash # Query entries with nested relations curl -X POST https://example.com/api \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN_HERE" \ -d '{ "query": "query { entries(section: \"blog\", limit: 5) { id title slug postDate @formatDateTime(format: \"Y-m-d\") author { id fullName email } ... on blog_article_Entry { bodyContent featuredImage { id url width height } tags { id title } } } }" }' # Response structure # { # "data": { # "entries": [ # { # "id": "123", # "title": "Article Title", # "slug": "article-title", # "postDate": "2025-01-15", # "author": { # "id": "1", # "fullName": "John Doe", # "email": "john@example.com" # }, # "bodyContent": "<p>Content here</p>", # "featuredImage": [ # { # "id": "42", # "url": "https://example.com/uploads/image.jpg", # "width": 1920, # "height": 1080 # } # ], # "tags": [ # { "id": "10", "title": "Technology" } # ] # } # ] # } # } ``` ### GraphQL Mutation for Saving Content Create or update entries via GraphQL mutations. ```graphql mutation { save_blog_article_Entry( title: "New Blog Post" slug: "new-blog-post" postDate: "2025-01-20 14:30:00" bodyContent: "<p>This is the article body.</p>" featuredImage: [42] tags: [10, 11] ) { id title slug dateCreated @formatDateTime(format: "Y-m-d H:i:s") errors { attribute message } } } ``` ### Register Custom Element Type via Plugin Extend Craft with custom element types for specialized content structures. ```php use craft\events\RegisterComponentTypesEvent; use craft\services\Elements; use yii\base\Event; // In your plugin's init() method Event::on( Elements::class, Elements::EVENT_REGISTER_ELEMENT_TYPES, function(RegisterComponentTypesEvent $event) { $event->types[] = \myplugin\elements\Product::class; } ); // Custom element class example namespace myplugin\elements; use craft\base\Element; use craft\elements\db\ElementQueryInterface; class Product extends Element { public static function displayName(): string { return 'Product'; } public static function hasContent(): bool { return true; } public static function hasTitles(): bool { return true; } public static function find(): ElementQueryInterface { return new ProductQuery(static::class); } protected static function defineSources(string $context): array { return [ [ 'key' => '*', 'label' => 'All Products', ], ]; } } ``` ### Twig Template Rendering Render Twig templates with variables for frontend or control panel views. ```php // In a controller action public function actionView(): \yii\web\Response { $entry = \craft\elements\Entry::find() ->section('blog') ->slug('my-article') ->one(); if (!$entry) { throw new \yii\web\NotFoundHttpException('Entry not found'); } return $this->renderTemplate('blog/_detail.twig', [ 'entry' => $entry, 'relatedEntries' => $entry->relatedPosts->limit(3)->all(), 'siteUrl' => \craft\helpers\UrlHelper::siteUrl(), ]); } ``` ```twig {# In blog/_detail.twig template #} {% extends "_layout" %} {% block content %} <article> <h1>{{ entry.title }}</h1> <time datetime="{{ entry.postDate|date('Y-m-d') }}"> {{ entry.postDate|date('F j, Y') }} </time> <div class="author"> By {{ entry.author.fullName }} </div> <div class="content"> {{ entry.bodyContent|raw }} </div> {% if entry.featuredImage|length %} <img src="{{ entry.featuredImage.one().url }}" alt="{{ entry.featuredImage.one().title }}"> {% endif %} {% if relatedEntries|length %} <h2>Related Articles</h2> <ul> {% for related in relatedEntries %} <li> <a href="{{ related.url }}">{{ related.title }}</a> </li> {% endfor %} </ul> {% endif %} </article> {% endblock %} ``` ### Asset Upload and Management Upload files to asset volumes and manipulate images. ```php use craft\elements\Asset; use craft\helpers\Assets; use Craft; // Upload a file from form submission $volumeId = 2; // Target volume $folderId = 5; // Target folder within volume $file = $_FILES['upload']; $filename = Assets::prepareAssetName($file['name']); $asset = new Asset(); $asset->tempFilePath = $file['tmp_name']; $asset->filename = $filename; $asset->newFolderId = $folderId; $asset->volumeId = $volumeId; $asset->avoidFilenameConflicts = true; // Set custom fields on asset $asset->setFieldValue('altText', 'Description of image'); $asset->setFieldValue('photographer', 'John Smith'); if (Craft::$app->getElements()->saveElement($asset)) { echo "Asset uploaded: {$asset->url}"; // Generate image transform $transform = Craft::$app->imageTransforms->getTransformByHandle('thumbnail'); $transformUrl = $asset->getUrl($transform); echo "Thumbnail URL: {$transformUrl}"; } else { print_r($asset->getErrors()); } // Query existing assets $images = Asset::find() ->volumeHandle('images') ->kind('image') ->filename('*.jpg') ->limit(20) ->all(); ``` ### User Authentication and Registration Authenticate users and manage user accounts programmatically. ```php use craft\elements\User; use Craft; // Register a new user $user = new User(); $user->username = 'newuser'; $user->email = 'newuser@example.com'; $user->firstName = 'John'; $user->lastName = 'Doe'; $user->newPassword = 'securePassword123!'; // Assign to user group $group = Craft::$app->userGroups->getGroupByHandle('members'); if ($group) { Craft::$app->users->assignUserToGroups($user->id, [$group->id]); } if (Craft::$app->elements->saveElement($user)) { // Activate the user account Craft::$app->users->activateUser($user); // Send activation email Craft::$app->users->sendActivationEmail($user); echo "User created with ID: {$user->id}"; } else { print_r($user->getErrors()); } // Authenticate user login $username = 'newuser'; $password = 'securePassword123!'; if (Craft::$app->getUser()->login( Craft::$app->users->getUserByUsernameOrEmail($username), 3600 * 24 * 30 // 30 days )) { echo "User logged in successfully"; } else { echo "Invalid credentials"; } // Check current user permissions $currentUser = Craft::$app->getUser()->getIdentity(); if ($currentUser && $currentUser->can('editEntries:5')) { echo "User can edit entries in section 5"; } ``` ### Plugin Configuration and Settings Create a plugin with custom settings and service components. ```php namespace myplugin; use craft\base\Plugin; use myplugin\services\MyService; use myplugin\models\Settings; class MyPlugin extends Plugin { public static function config(): array { return [ 'components' => [ 'myService' => ['class' => MyService::class], ], ]; } public function init() { parent::init(); // Register event listeners \yii\base\Event::on( \craft\elements\Entry::class, \craft\elements\Entry::EVENT_AFTER_SAVE, function(\craft\events\ElementEvent $event) { $entry = $event->element; // Custom logic after entry save $this->myService->processEntry($entry); } ); } protected function createSettingsModel(): ?\craft\base\Model { return new Settings(); } protected function settingsHtml(): ?string { return \Craft::$app->view->renderTemplate( 'my-plugin/settings', ['settings' => $this->getSettings()] ); } } // Access plugin instance and services $plugin = \myplugin\MyPlugin::getInstance(); $result = $plugin->myService->someMethod(); // Get plugin settings $settings = $plugin->getSettings(); echo $settings->apiKey; ``` ### Register GraphQL Queries and Types Extend the GraphQL schema with custom queries and types. ```php use craft\events\RegisterGqlQueriesEvent; use craft\events\RegisterGqlTypesEvent; use craft\services\Gql; use yii\base\Event; // Register custom query Event::on( Gql::class, Gql::EVENT_REGISTER_GQL_QUERIES, function(RegisterGqlQueriesEvent $event) { $event->queries['myProducts'] = [ 'type' => \GraphQL\Type\Definition\Type::listOf( \myplugin\gql\types\Product::getType() ), 'args' => [ 'limit' => \GraphQL\Type\Definition\Type::int(), 'category' => \GraphQL\Type\Definition\Type::string(), ], 'resolve' => function($source, $args) { return \myplugin\elements\Product::find() ->category($args['category'] ?? null) ->limit($args['limit'] ?? 10) ->all(); }, ]; } ); // Register custom type Event::on( Gql::class, Gql::EVENT_REGISTER_GQL_TYPES, function(RegisterGqlTypesEvent $event) { $event->types[] = \myplugin\gql\types\Product::class; } ); // Query usage // { // myProducts(limit: 5, category: "electronics") { // id // title // price // } // } ``` ### Element Queries with Relations Eager load related elements to optimize database queries. ```php // Eager load multiple levels of relations $entries = \craft\elements\Entry::find() ->section('blog') ->with([ 'author', 'featuredImage', ['relatedProducts', ['manufacturer']], ]) ->all(); // Access eager-loaded relations without additional queries foreach ($entries as $entry) { echo $entry->title; echo $entry->author->fullName; // No query if ($entry->featuredImage) { foreach ($entry->featuredImage->all() as $image) { echo $image->url; // No query } } foreach ($entry->relatedProducts->all() as $product) { echo $product->title; // No query echo $product->manufacturer->one()->name; // No query } } // Matrix field eager loading $entries = \craft\elements\Entry::find() ->section('pages') ->with(['contentBlocks.image']) ->all(); foreach ($entries as $entry) { foreach ($entry->contentBlocks->all() as $block) { echo $block->heading; if ($block->image->one()) { echo $block->image->one()->url; } } } ``` ### Database Migrations Create database migrations for plugin installation and updates. ```php namespace myplugin\migrations; use Craft; use craft\db\Migration; class Install extends Migration { public function safeUp() { // Create custom table $this->createTable('{{%myplugin_products}}', [ 'id' => $this->primaryKey(), 'name' => $this->string()->notNull(), 'price' => $this->decimal(10, 2)->notNull(), 'sku' => $this->string(50)->notNull(), 'stock' => $this->integer()->defaultValue(0), 'dateCreated' => $this->dateTime()->notNull(), 'dateUpdated' => $this->dateTime()->notNull(), 'uid' => $this->uid(), ]); // Create index $this->createIndex( 'myplugin_products_sku_idx', '{{%myplugin_products}}', 'sku', true // unique ); // Create foreign key $this->addForeignKey( 'myplugin_products_id_fk', '{{%myplugin_products}}', 'id', '{{%elements}}', 'id', 'CASCADE' ); return true; } public function safeDown() { $this->dropTableIfExists('{{%myplugin_products}}'); return true; } } // Run migrations programmatically $migrator = Craft::$app->contentMigrator; $migrator->up(); ``` ### Template Fragment Caching Cache expensive template operations for performance optimization. ```twig {# Cache for 1 hour (3600 seconds) #} {% cache for 1 hour %} {% set products = craft.entries() .section('products') .limit(20) .all() %} <ul> {% for product in products %} <li>{{ product.title }} - {{ product.price }}</li> {% endfor %} </ul> {% endcache %} {# Cache with dynamic keys based on segment and site #} {% cache using key "blog-list-#{currentUser.id}" for 30 minutes %} {% set entries = craft.entries() .section('blog') .authorId(currentUser.id) .all() %} {% for entry in entries %} <article> <h2>{{ entry.title }}</h2> {{ entry.summary }} </article> {% endfor %} {% endcache %} {# Cache with conditions #} {% cache unless currentUser if craft.app.request.getQueryParam('nocache') is empty %} {# Expensive operations here #} {% endcache %} ``` ### Field Type Definition Create custom field types for specialized content input. ```php namespace myplugin\fields; use Craft; use craft\base\Field; use craft\base\ElementInterface; class ColorPicker extends Field { public static function displayName(): string { return 'Color Picker'; } public static function icon(): string { return '@myplugin/icon.svg'; } public function getContentColumnType(): string { return \yii\db\Schema::TYPE_STRING; } public function normalizeValue($value, ElementInterface $element = null) { if (is_string($value) && preg_match('/^#[0-9a-f]{6}$/i', $value)) { return $value; } return null; } public function getInputHtml($value, ElementInterface $element = null): string { return Craft::$app->getView()->renderTemplate( 'my-plugin/_fields/color-picker/input', [ 'name' => $this->handle, 'value' => $value, 'field' => $this, ] ); } public function getSearchKeywords($value, ElementInterface $element): string { return (string) $value; } } // Register the field type use craft\events\RegisterComponentTypesEvent; use craft\services\Fields; use yii\base\Event; Event::on( Fields::class, Fields::EVENT_REGISTER_FIELD_TYPES, function(RegisterComponentTypesEvent $event) { $event->types[] = \myplugin\fields\ColorPicker::class; } ); ``` ### URL Routing Configuration Define custom URL routes for controllers and templates. ```php // In config/routes.php return [ 'blog' => 'templates/render', 'blog/<slug:{slug}>' => 'templates/render', 'api/products' => 'my-plugin/api/products', 'api/products/<id:\d+>' => 'my-plugin/api/product-detail', ]; // Dynamic route registration in plugin use craft\events\RegisterUrlRulesEvent; use craft\web\UrlManager; use yii\base\Event; Event::on( UrlManager::class, UrlManager::EVENT_REGISTER_SITE_URL_RULES, function(RegisterUrlRulesEvent $event) { $event->rules['search'] = 'my-plugin/search/index'; $event->rules['search/<query:.+>'] = 'my-plugin/search/results'; } ); // Controller action namespace myplugin\controllers; use craft\web\Controller; class SearchController extends Controller { protected array|bool|int $allowAnonymous = ['index', 'results']; public function actionResults(string $query): \yii\web\Response { $results = \craft\elements\Entry::find() ->search($query) ->limit(20) ->all(); return $this->renderTemplate('my-plugin/search/results', [ 'query' => $query, 'results' => $results, ]); } } ``` --- ## Main Use Cases and Integration Patterns Craft CMS excels in scenarios requiring flexible content modeling without rigid schemas. Content teams benefit from the intuitive control panel to manage entries, assets, and users across multiple sites and languages. The element-query system enables developers to build complex content relationships with efficient eager loading, while custom field types extend the platform to handle specialized data like color pickers, address fields, or embedded media. The GraphQL API auto-generates schemas based on configured sections and fields, making it ideal for headless deployments powering mobile apps, SPAs, or IoT devices. For integration patterns, plugins extend functionality through event listeners, custom services, and new element types. The migration system ensures database changes are version-controlled and deployable. Template caching and query optimization features handle high-traffic scenarios. Multi-environment configuration uses environment variables parsed via `App::parseEnv()` for database credentials, API keys, and feature flags. Project Config YAML files sync content structure across environments via Git. The service layer (accessed via `Craft::$app->serviceName`) provides a consistent API for all operations, from saving elements to sending emails, making Craft highly testable and maintainable for large-scale applications.