Repository: statamic/docs Branch: 6.x Commit: 7bf3cec2b499 Files: 939 Total size: 2.4 MB Directory structure: gitextract_4rmyxzvf/ ├── .editorconfig ├── .gitattributes ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── .nvmrc ├── LICENSE.md ├── README.md ├── app/ │ ├── Http/ │ │ ├── Controllers/ │ │ │ ├── Controller.php │ │ │ ├── DocsMarkdownController.php │ │ │ └── LlmsTxtController.php │ │ └── Middleware/ │ │ └── CheckForMaintenanceMode.php │ ├── Markdown/ │ │ ├── Hint/ │ │ │ ├── Hint.php │ │ │ ├── HintExtension.php │ │ │ ├── HintParser.php │ │ │ ├── HintRenderer.php │ │ │ └── HintStartParser.php │ │ └── Tabs/ │ │ ├── TabbedCodeBlock.php │ │ ├── TabbedCodeBlockExtension.php │ │ ├── TabbedCodeStartParser.php │ │ ├── TabsParser.php │ │ └── TabsRenderer.php │ ├── Models/ │ │ └── User.php │ ├── Modifiers/ │ │ ├── Split.php │ │ └── Toc.php │ ├── Providers/ │ │ └── AppServiceProvider.php │ ├── Search/ │ │ ├── DocTransformer.php │ │ ├── Listeners/ │ │ │ └── SearchEntriesCreatedListener.php │ │ ├── RequestContentRetriever.php │ │ └── Storybook/ │ │ ├── StorybookSearchProvider.php │ │ └── StorybookSearchable.php │ ├── Tags/ │ │ ├── GithubCommitsUrl.php │ │ ├── GithubEditUrl.php │ │ └── HeroSponsors.php │ └── ViewModels/ │ ├── Fieldtypes.php │ ├── Modifiers.php │ ├── Tags.php │ └── Variables.php ├── artisan ├── bootstrap/ │ ├── app.php │ ├── cache/ │ │ └── .gitignore │ └── providers.php ├── composer.json ├── config/ │ ├── app.php │ ├── auth.php │ ├── cache.php │ ├── database.php │ ├── docs.php │ ├── filesystems.php │ ├── logging.php │ ├── mail.php │ ├── queue.php │ ├── services.php │ ├── session.php │ ├── statamic/ │ │ ├── antlers.php │ │ ├── api.php │ │ ├── assets.php │ │ ├── autosave.php │ │ ├── cp.php │ │ ├── editions.php │ │ ├── forms.php │ │ ├── git.php │ │ ├── graphql.php │ │ ├── live_preview.php │ │ ├── markdown.php │ │ ├── oauth.php │ │ ├── protect.php │ │ ├── revisions.php │ │ ├── routes.php │ │ ├── search.php │ │ ├── stache.php │ │ ├── static_caching.php │ │ ├── system.php │ │ ├── templates.php │ │ ├── users.php │ │ └── webauthn.php │ └── torchlight.php ├── content/ │ ├── assets/ │ │ ├── .gitkeep │ │ └── main.yaml │ ├── collections/ │ │ ├── .gitkeep │ │ ├── fieldtypes/ │ │ │ ├── array.md │ │ │ ├── assets.md │ │ │ ├── bard.md │ │ │ ├── button_group.md │ │ │ ├── checkboxes.md │ │ │ ├── code.md │ │ │ ├── collections.md │ │ │ ├── color.md │ │ │ ├── date.md │ │ │ ├── dictionary.md │ │ │ ├── entries.md │ │ │ ├── float.md │ │ │ ├── form.md │ │ │ ├── grid.md │ │ │ ├── group.md │ │ │ ├── hidden.md │ │ │ ├── html.md │ │ │ ├── icon.md │ │ │ ├── integer.md │ │ │ ├── link.md │ │ │ ├── list.md │ │ │ ├── markdown.md │ │ │ ├── navs.md │ │ │ ├── radio.md │ │ │ ├── range.md │ │ │ ├── replicator.md │ │ │ ├── revealer.md │ │ │ ├── select.md │ │ │ ├── sites.md │ │ │ ├── slug.md │ │ │ ├── spacer.md │ │ │ ├── structures.md │ │ │ ├── table.md │ │ │ ├── taggable.md │ │ │ ├── taxonomies.md │ │ │ ├── template.md │ │ │ ├── terms.md │ │ │ ├── text.md │ │ │ ├── textarea.md │ │ │ ├── time.md │ │ │ ├── toggle.md │ │ │ ├── user-groups.md │ │ │ ├── user-roles.md │ │ │ ├── users.md │ │ │ ├── video.md │ │ │ ├── width.md │ │ │ └── yaml.md │ │ ├── fieldtypes.yaml │ │ ├── modifiers/ │ │ │ ├── add.md │ │ │ ├── add_slashes.md │ │ │ ├── ampersand_list.md │ │ │ ├── antlers.md │ │ │ ├── as.md │ │ │ ├── ascii.md │ │ │ ├── at.md │ │ │ ├── attribute.md │ │ │ ├── background_position.md │ │ │ ├── backspace.md │ │ │ ├── bard_html.md │ │ │ ├── bard_items.md │ │ │ ├── bard_text.md │ │ │ ├── bool_string.md │ │ │ ├── camelize.md │ │ │ ├── cdata.md │ │ │ ├── ceil.md │ │ │ ├── chunk.md │ │ │ ├── classes.md │ │ │ ├── collapse.md │ │ │ ├── collapse_whitespace.md │ │ │ ├── compact.md │ │ │ ├── console_log.md │ │ │ ├── contains.md │ │ │ ├── contains_all.md │ │ │ ├── contains_any.md │ │ │ ├── count.md │ │ │ ├── count_substring.md │ │ │ ├── dashify.md │ │ │ ├── days_ago.md │ │ │ ├── decode.md │ │ │ ├── deslugify.md │ │ │ ├── divide.md │ │ │ ├── dl.md │ │ │ ├── doesnt_overlap.md │ │ │ ├── dump.md │ │ │ ├── embed_url.md │ │ │ ├── ends_with.md │ │ │ ├── ensure_left.md │ │ │ ├── ensure_right.md │ │ │ ├── entities.md │ │ │ ├── excerpt.md │ │ │ ├── explode.md │ │ │ ├── favicon.md │ │ │ ├── filter_empty.md │ │ │ ├── first.md │ │ │ ├── flatten.md │ │ │ ├── flip.md │ │ │ ├── floor.md │ │ │ ├── folder.yaml │ │ │ ├── format.md │ │ │ ├── format_number.md │ │ │ ├── format_translated.md │ │ │ ├── full_urls.md │ │ │ ├── get.md │ │ │ ├── gravatar.md │ │ │ ├── group_by.md │ │ │ ├── has_lower_case.md │ │ │ ├── has_upper_case.md │ │ │ ├── headline.md │ │ │ ├── hex_to_rgb.md │ │ │ ├── hours_ago.md │ │ │ ├── image.md │ │ │ ├── in_array.md │ │ │ ├── insert.md │ │ │ ├── is_after.md │ │ │ ├── is_alpha.md │ │ │ ├── is_alphanumeric.md │ │ │ ├── is_array.md │ │ │ ├── is_before.md │ │ │ ├── is_between.md │ │ │ ├── is_blank.md │ │ │ ├── is_email.md │ │ │ ├── is_embeddable.md │ │ │ ├── is_empty.md │ │ │ ├── is_external_url.md │ │ │ ├── is_future.md │ │ │ ├── is_json.md │ │ │ ├── is_leap_year.md │ │ │ ├── is_lowercase.md │ │ │ ├── is_numberwang.md │ │ │ ├── is_numeric.md │ │ │ ├── is_past.md │ │ │ ├── is_today.md │ │ │ ├── is_tomorrow.md │ │ │ ├── is_uppercase.md │ │ │ ├── is_url.md │ │ │ ├── is_weekday.md │ │ │ ├── is_weekend.md │ │ │ ├── is_yesterday.md │ │ │ ├── iso_format.md │ │ │ ├── join.md │ │ │ ├── kebab.md │ │ │ ├── key_by.md │ │ │ ├── keys.md │ │ │ ├── last.md │ │ │ ├── lcfirst.md │ │ │ ├── length.md │ │ │ ├── limit.md │ │ │ ├── link.md │ │ │ ├── list.md │ │ │ ├── lower.md │ │ │ ├── macro.md │ │ │ ├── mailto.md │ │ │ ├── mark.md │ │ │ ├── markdown.md │ │ │ ├── md5.md │ │ │ ├── merge.md │ │ │ ├── minutes_ago.md │ │ │ ├── mod.md │ │ │ ├── modify_date.md │ │ │ ├── months_ago.md │ │ │ ├── multiply.md │ │ │ ├── nl2br.md │ │ │ ├── obfuscate.md │ │ │ ├── obfuscate_email.md │ │ │ ├── offset.md │ │ │ ├── ol.md │ │ │ ├── option_list.md │ │ │ ├── output.md │ │ │ ├── overlaps.md │ │ │ ├── pad.md │ │ │ ├── parse_url.md │ │ │ ├── partial.md │ │ │ ├── pathinfo.md │ │ │ ├── piped.md │ │ │ ├── pluck.md │ │ │ ├── plural.md │ │ │ ├── random.md │ │ │ ├── raw.md │ │ │ ├── rawurlencode.md │ │ │ ├── rawurlencode_except_slashes.md │ │ │ ├── ray.md │ │ │ ├── read_time.md │ │ │ ├── regex_mark.md │ │ │ ├── regex_replace.md │ │ │ ├── relative.md │ │ │ ├── remove_left.md │ │ │ ├── remove_right.md │ │ │ ├── repeat.md │ │ │ ├── replace.md │ │ │ ├── resolve.md │ │ │ ├── reverse.md │ │ │ ├── round.md │ │ │ ├── safe_truncate.md │ │ │ ├── sanitize.md │ │ │ ├── scope.md │ │ │ ├── seconds_ago.md │ │ │ ├── segment.md │ │ │ ├── select.md │ │ │ ├── sentence_list.md │ │ │ ├── shuffle.md │ │ │ ├── singular.md │ │ │ ├── slugify.md │ │ │ ├── smartypants.md │ │ │ ├── snake.md │ │ │ ├── sort.md │ │ │ ├── spaceless.md │ │ │ ├── split.md │ │ │ ├── starts_with.md │ │ │ ├── str_pad_left.md │ │ │ ├── strip_tags.md │ │ │ ├── studly.md │ │ │ ├── substr.md │ │ │ ├── subtract.md │ │ │ ├── sum.md │ │ │ ├── surround.md │ │ │ ├── swap_case.md │ │ │ ├── table.md │ │ │ ├── tidy.md │ │ │ ├── timezone.md │ │ │ ├── title.md │ │ │ ├── to_json.md │ │ │ ├── to_qs.md │ │ │ ├── to_spaces.md │ │ │ ├── to_tabs.md │ │ │ ├── trans.md │ │ │ ├── trim.md │ │ │ ├── truncate.md │ │ │ ├── ucfirst.md │ │ │ ├── ul.md │ │ │ ├── underscored.md │ │ │ ├── unique.md │ │ │ ├── upper.md │ │ │ ├── url.md │ │ │ ├── urldecode.md │ │ │ ├── urlencode.md │ │ │ ├── urlencode_except_slashes.md │ │ │ ├── values.md │ │ │ ├── weeks_ago.md │ │ │ ├── where-in.md │ │ │ ├── where.md │ │ │ ├── widont.md │ │ │ ├── word_count.md │ │ │ ├── wrap.md │ │ │ └── years_ago.md │ │ ├── modifiers.yaml │ │ ├── pages/ │ │ │ ├── 3-0-to-3-1.md │ │ │ ├── 3-1-to-3-2.md │ │ │ ├── 3-2-to-3-3.md │ │ │ ├── 3-3-to-3-4.md │ │ │ ├── 3-4-to-4-0.md │ │ │ ├── 4-to-5.md │ │ │ ├── 5-to-6.md │ │ │ ├── actions.md │ │ │ ├── addons.md │ │ │ ├── advanced-topics.md │ │ │ ├── all-fieldtypes.md │ │ │ ├── all-modifiers.md │ │ │ ├── all-tags.md │ │ │ ├── all-variables.md │ │ │ ├── all-widgets.md │ │ │ ├── antlers.md │ │ │ ├── assets.md │ │ │ ├── augmentation.md │ │ │ ├── backend-apis.md │ │ │ ├── bard-v1-to-v2.md │ │ │ ├── blade-form-fields.md │ │ │ ├── blade.md │ │ │ ├── blink-cache.md │ │ │ ├── blueprints.md │ │ │ ├── build-a-fieldtype.md │ │ │ ├── building-a-tag.md │ │ │ ├── building-a-widget.md │ │ │ ├── building-an-addon.md │ │ │ ├── caching.md │ │ │ ├── cli.md │ │ │ ├── code-of-conduct.md │ │ │ ├── collections.md │ │ │ ├── command-palette.md │ │ │ ├── computed-values.md │ │ │ ├── conditional-fields.md │ │ │ ├── conditions.md │ │ │ ├── configuration.md │ │ │ ├── content-managers-guide.md │ │ │ ├── content-modeling.md │ │ │ ├── content-queries.md │ │ │ ├── contributing.md │ │ │ ├── contribution-guide.md │ │ │ ├── control-panel.md │ │ │ ├── controllers.md │ │ │ ├── core-concepts.md │ │ │ ├── cp-navigation.md │ │ │ ├── cp-translations.md │ │ │ ├── creating-a-starter-kit.md │ │ │ ├── css-javascript.md │ │ │ ├── customizing-the-cp-nav.md │ │ │ ├── dashboard.md │ │ │ ├── data-inheritance.md │ │ │ ├── data.md │ │ │ ├── debugging.md │ │ │ ├── deploying.md │ │ │ ├── dictionaries.md │ │ │ ├── digital-ocean.md │ │ │ ├── digitalocean.md │ │ │ ├── dirty-state-tracking.md │ │ │ ├── docker.md │ │ │ ├── elevated-sessions.md │ │ │ ├── email.md │ │ │ ├── events.md │ │ │ ├── field-actions.md │ │ │ ├── fields.md │ │ │ ├── fieldsets.md │ │ │ ├── fieldtypes.1.md │ │ │ ├── formatters.md │ │ │ ├── forms.md │ │ │ ├── fortrabbit.md │ │ │ ├── from-wordpress-to-statamic.md │ │ │ ├── frontend.md │ │ │ ├── getting-started.md │ │ │ ├── git-automation.md │ │ │ ├── globals.md │ │ │ ├── graphql.md │ │ │ ├── home.md │ │ │ ├── hooks.md │ │ │ ├── image-manipulation.md │ │ │ ├── installing-a-starter-kit.md │ │ │ ├── installing.md │ │ │ ├── javascript-frameworks.md │ │ │ ├── js-events.md │ │ │ ├── js-hooks.md │ │ │ ├── keyboard-shortcuts.md │ │ │ ├── knowledge-base.md │ │ │ ├── laravel-7-to-8.md │ │ │ ├── laravel-cloud.md │ │ │ ├── laravel-forge-1-click.md │ │ │ ├── laravel-forge.md │ │ │ ├── laravel-herd.md │ │ │ ├── laravel.md │ │ │ ├── licensing.md │ │ │ ├── lifecycle.md │ │ │ ├── linode.md │ │ │ ├── live-preview.md │ │ │ ├── local.md │ │ │ ├── markdown.md │ │ │ ├── modifiers.1.md │ │ │ ├── modifiers.md │ │ │ ├── multi-site.md │ │ │ ├── multi-user-collaboration.md │ │ │ ├── navigation.md │ │ │ ├── netlify.md │ │ │ ├── oauth.md │ │ │ ├── overview.1.md │ │ │ ├── overview.10.md │ │ │ ├── overview.12.md │ │ │ ├── overview.2.md │ │ │ ├── overview.3.md │ │ │ ├── overview.4.md │ │ │ ├── overview.5.md │ │ │ ├── overview.6.md │ │ │ ├── overview.7.md │ │ │ ├── overview.8.md │ │ │ ├── overview.md │ │ │ ├── permissions.md │ │ │ ├── ploi.md │ │ │ ├── preferences.md │ │ │ ├── progress.md │ │ │ ├── protecting-content.md │ │ │ ├── publish-forms.md │ │ │ ├── query-scopes-and-filters.md │ │ │ ├── quick-start-guide.md │ │ │ ├── relationship-fieldtypes.md │ │ │ ├── relationships.md │ │ │ ├── release-schedule-support-policy.md │ │ │ ├── repositories.md │ │ │ ├── requirements.md │ │ │ ├── resource-apis.md │ │ │ ├── rest-api.md │ │ │ ├── revisions.md │ │ │ ├── routing.1.md │ │ │ ├── routing.md │ │ │ ├── scheduling.md │ │ │ ├── search.md │ │ │ ├── sites-api.md │ │ │ ├── slugs.md │ │ │ ├── stache.md │ │ │ ├── starter-kits.md │ │ │ ├── static-caching.md │ │ │ ├── structures.md │ │ │ ├── tags.md │ │ │ ├── taxonomies.md │ │ │ ├── testing.md │ │ │ ├── tips.md │ │ │ ├── toast-notifications.md │ │ │ ├── troubleshooting.md │ │ │ ├── ubuntu.md │ │ │ ├── ui-components.md │ │ │ ├── updating-a-starter-kit.md │ │ │ ├── updating.md │ │ │ ├── upgrade-guide.md │ │ │ ├── users.md │ │ │ ├── utilities.md │ │ │ ├── v2-to-v3.md │ │ │ ├── validation.md │ │ │ ├── variables.md │ │ │ ├── vercel.md │ │ │ ├── view-models.md │ │ │ ├── views.md │ │ │ ├── vite-tooling.md │ │ │ ├── vue-2-to-3.md │ │ │ ├── vue-components.md │ │ │ ├── white-labeling.md │ │ │ ├── widgets.md │ │ │ └── yaml.md │ │ ├── pages.yaml │ │ ├── resource_apis/ │ │ │ ├── asset-container-repository.md │ │ │ ├── asset-repository.md │ │ │ ├── collection-repository.md │ │ │ ├── entry-repository.md │ │ │ ├── form-repository.md │ │ │ ├── form-submission-repository.md │ │ │ ├── global-repository.md │ │ │ ├── site-repository.md │ │ │ ├── taxonomy-repository.md │ │ │ ├── term-repository.md │ │ │ ├── user-group-repository.md │ │ │ ├── user-repository.md │ │ │ └── user-role-repository.md │ │ ├── resource_apis.yaml │ │ ├── tags/ │ │ │ ├── 404.md │ │ │ ├── asset.md │ │ │ ├── assets.md │ │ │ ├── cache.md │ │ │ ├── children.md │ │ │ ├── collection-count.md │ │ │ ├── collection-next.md │ │ │ ├── collection-previous.md │ │ │ ├── collection.md │ │ │ ├── cookie.md │ │ │ ├── dictionary.md │ │ │ ├── dump.md │ │ │ ├── foreach.md │ │ │ ├── form-create.md │ │ │ ├── form-errors.md │ │ │ ├── form-fields.md │ │ │ ├── form-set.md │ │ │ ├── form-submission.md │ │ │ ├── form-submissions.md │ │ │ ├── form-success.md │ │ │ ├── form.md │ │ │ ├── get_content.md │ │ │ ├── get_error.md │ │ │ ├── get_errors.md │ │ │ ├── get_files.md │ │ │ ├── get_site.md │ │ │ ├── glide-batch.md │ │ │ ├── glide-data-url.md │ │ │ ├── glide.md │ │ │ ├── increment.md │ │ │ ├── installed.md │ │ │ ├── link.md │ │ │ ├── locales-count.md │ │ │ ├── locales.md │ │ │ ├── loop.md │ │ │ ├── markdown-indent.md │ │ │ ├── markdown.md │ │ │ ├── mix.md │ │ │ ├── mount_url.md │ │ │ ├── nav-breadcrumbs.md │ │ │ ├── nav.md │ │ │ ├── nocache.md │ │ │ ├── oauth.md │ │ │ ├── obfuscate.md │ │ │ ├── parent.md │ │ │ ├── partial-exists.md │ │ │ ├── partial-if-exists.md │ │ │ ├── partial.md │ │ │ ├── protect-password_form.md │ │ │ ├── redirect.md │ │ │ ├── route.md │ │ │ ├── scope.md │ │ │ ├── search.md │ │ │ ├── section.md │ │ │ ├── session-dump.md │ │ │ ├── session-flash.md │ │ │ ├── session-flush.md │ │ │ ├── session-forget.md │ │ │ ├── session-has.md │ │ │ ├── session-set.md │ │ │ ├── session.md │ │ │ ├── svg.md │ │ │ ├── switch.md │ │ │ ├── taxonomy-count.md │ │ │ ├── taxonomy.md │ │ │ ├── trans.md │ │ │ ├── user-can.md │ │ │ ├── user-delete_passkey_form.md │ │ │ ├── user-disable_two_factor_form.md │ │ │ ├── user-elevated_session_form.md │ │ │ ├── user-forgot_password_form.md │ │ │ ├── user-groups.md │ │ │ ├── user-in.md │ │ │ ├── user-is.md │ │ │ ├── user-login_form.md │ │ │ ├── user-logout.md │ │ │ ├── user-logout_url.md │ │ │ ├── user-passkey_form.md │ │ │ ├── user-passkeys.md │ │ │ ├── user-password_form.md │ │ │ ├── user-profile.md │ │ │ ├── user-profile_form.md │ │ │ ├── user-register_form.md │ │ │ ├── user-reset_password_form.md │ │ │ ├── user-reset_two_factor_recovery_codes_form.md │ │ │ ├── user-roles.md │ │ │ ├── user-two_factor_challenge_form.md │ │ │ ├── user-two_factor_enable_form.md │ │ │ ├── user-two_factor_enabled.md │ │ │ ├── user-two_factor_recovery_codes.md │ │ │ ├── user-two_factor_recovery_codes_download_url.md │ │ │ ├── user-two_factor_setup_form.md │ │ │ ├── users.md │ │ │ ├── vite-content.md │ │ │ ├── vite.md │ │ │ └── yield.md │ │ ├── tags.yaml │ │ ├── tips/ │ │ │ ├── building-your-own-entries-repository.md │ │ │ ├── change-timezone-to-utc.md │ │ │ ├── configuring-update-scripts.md │ │ │ ├── content-security-policy.md │ │ │ ├── converting-from-single-to-multi-site.md │ │ │ ├── creating-users-by-hand.md │ │ │ ├── digital-ocean-spaces-for-asset-container.md │ │ │ ├── disabling-cp-authentication.md │ │ │ ├── excluding-the-control-panel-from-maintenance-mode.md │ │ │ ├── gdpr-considerations.md │ │ │ ├── git-workflow.md │ │ │ ├── how-to-enable-statamic-pro.md │ │ │ ├── importing-content.md │ │ │ ├── laravel-nova.md │ │ │ ├── load-templates-dynamically-based-on-the-url.md │ │ │ ├── localizing-entries.md │ │ │ ├── localizing-globals.md │ │ │ ├── localizing-navigation.md │ │ │ ├── manually-resetting-a-user-password.md │ │ │ ├── optimizing-assets.md │ │ │ ├── overriding-exception-rendering.md │ │ │ ├── pushing-related-entries-to-an-algolia-index.md │ │ │ ├── recursive-nav-examples.md │ │ │ ├── reserved-words.md │ │ │ ├── setting-default-listing-columns.md │ │ │ ├── storing-content-in-a-database.md │ │ │ ├── storing-laravel-users-in-files.md │ │ │ ├── storing-users-in-a-database.md │ │ │ ├── storing-users-somewhere-custom.md │ │ │ ├── taxonomies-by-hand.md │ │ │ ├── timezones.md │ │ │ ├── trailing-slashes.md │ │ │ ├── using-an-independent-authentication-guard.md │ │ │ ├── using-statamic-with-laravel-nightwatch.md │ │ │ └── zero-downtime-deployments.md │ │ ├── tips.yaml │ │ ├── troubleshooting/ │ │ │ ├── asset-permissions.md │ │ │ ├── assets-missing-urls.md │ │ │ ├── command-not-found-statamic.md │ │ │ ├── composer-and-github-authentication.md │ │ │ ├── control-panel-page-expired.md │ │ │ ├── email-not-sending.md │ │ │ ├── fixing-issues-with-global-composer-packages.md │ │ │ ├── listing-performance.md │ │ │ ├── missing-control-panel-assets.md │ │ │ └── required-php-extensions.md │ │ ├── troubleshooting.yaml │ │ ├── variables/ │ │ │ ├── basename.md │ │ │ ├── collection.md │ │ │ ├── config.md │ │ │ ├── csrf_field.md │ │ │ ├── csrf_token.md │ │ │ ├── current_layout.md │ │ │ ├── current_template.md │ │ │ ├── current_uri.md │ │ │ ├── current_url.md │ │ │ ├── current_user.md │ │ │ ├── date.md │ │ │ ├── datestamp.md │ │ │ ├── datestring.md │ │ │ ├── edit_url.md │ │ │ ├── entries_count.md │ │ │ ├── environment.md │ │ │ ├── extension.md │ │ │ ├── filename.md │ │ │ ├── focus.md │ │ │ ├── focus_css.md │ │ │ ├── folder.yaml │ │ │ ├── get.md │ │ │ ├── get_post.md │ │ │ ├── has_timestamp.md │ │ │ ├── height.md │ │ │ ├── homepage.md │ │ │ ├── id.md │ │ │ ├── is_asset.md │ │ │ ├── is_entry.md │ │ │ ├── is_homepage.md │ │ │ ├── is_image.md │ │ │ ├── is_term.md │ │ │ ├── is_video.md │ │ │ ├── last_modified.md │ │ │ ├── last_segment.md │ │ │ ├── live_preview.md │ │ │ ├── logged_in.md │ │ │ ├── now.md │ │ │ ├── old.md │ │ │ ├── order.md │ │ │ ├── order_type.md │ │ │ ├── path.md │ │ │ ├── permalink.md │ │ │ ├── post.md │ │ │ ├── published.md │ │ │ ├── response_code.md │ │ │ ├── segment_x.md │ │ │ ├── site.md │ │ │ ├── sites.md │ │ │ ├── size.md │ │ │ ├── size_bytes.md │ │ │ ├── size_gigabytes.md │ │ │ ├── size_kilobytes.md │ │ │ ├── size_megabytes.md │ │ │ ├── slug.md │ │ │ ├── taxonomy.md │ │ │ ├── timestamp.md │ │ │ ├── url.md │ │ │ ├── width.md │ │ │ └── xml_header.md │ │ ├── variables.yaml │ │ ├── widgets/ │ │ │ ├── collection.md │ │ │ ├── form.md │ │ │ └── updater.md │ │ └── widgets.yaml │ ├── globals/ │ │ ├── .gitkeep │ │ ├── default/ │ │ │ └── global.yaml │ │ └── global.yaml │ ├── navigation/ │ │ └── .gitkeep │ ├── structures/ │ │ └── .gitkeep │ ├── taxonomies/ │ │ ├── .gitkeep │ │ ├── categories/ │ │ │ ├── cli.yaml │ │ │ ├── database.yaml │ │ │ ├── development.yaml │ │ │ ├── devops.yaml │ │ │ ├── laravel.yaml │ │ │ ├── localization.yaml │ │ │ ├── performance.yaml │ │ │ ├── privacy-gdpr.yaml │ │ │ └── troubleshooting.yaml │ │ ├── categories.yaml │ │ ├── modifier_types.yaml │ │ ├── tags/ │ │ │ └── beginner.yaml │ │ ├── tags.yaml │ │ ├── types/ │ │ │ ├── asset.yaml │ │ │ ├── content.yaml │ │ │ ├── entry.yaml │ │ │ ├── system.yaml │ │ │ └── term.yaml │ │ └── types.yaml │ └── trees/ │ ├── collections/ │ │ └── pages.yaml │ └── navigation/ │ ├── docs.yaml │ ├── extending_docs.yaml │ ├── guides.yaml │ ├── reference.yaml │ ├── screencasts.yaml │ ├── sections.yaml │ └── top.yaml ├── database/ │ ├── .gitignore │ ├── factories/ │ │ └── UserFactory.php │ ├── migrations/ │ │ ├── 0001_01_01_000000_create_users_table.php │ │ ├── 0001_01_01_000001_create_cache_table.php │ │ └── 0001_01_01_000002_create_jobs_table.php │ └── seeders/ │ └── DatabaseSeeder.php ├── lang/ │ └── en/ │ └── validation.php ├── package.json ├── phpunit.xml ├── please ├── postcss.config.js ├── public/ │ ├── .htaccess │ ├── favicons/ │ │ └── site.webmanifest │ ├── img/ │ │ ├── adverts/ │ │ │ └── .gitkeep │ │ ├── navigation/ │ │ │ └── .gitkeep │ │ ├── pro-badges/ │ │ │ ├── .gitkeep │ │ │ ├── artwork/ │ │ │ │ └── .gitkeep │ │ │ └── icons/ │ │ │ └── .gitkeep │ │ └── tiles/ │ │ └── .gitkeep │ ├── index.php │ ├── mix-manifest.json │ └── robots.txt ├── resources/ │ ├── blueprints/ │ │ ├── collections/ │ │ │ ├── adverts/ │ │ │ │ └── advert.yaml │ │ │ ├── extending-docs/ │ │ │ │ └── page.yaml │ │ │ ├── fieldtypes/ │ │ │ │ └── fieldtype.yaml │ │ │ ├── guides/ │ │ │ │ └── guides.yaml │ │ │ ├── knowledge-base/ │ │ │ │ └── page.yaml │ │ │ ├── modifiers/ │ │ │ │ └── modifiers.yaml │ │ │ ├── pages/ │ │ │ │ ├── home.yaml │ │ │ │ ├── link.yaml │ │ │ │ └── page.yaml │ │ │ ├── reference/ │ │ │ │ └── reference.yaml │ │ │ ├── references/ │ │ │ │ └── references.yaml │ │ │ ├── resource_apis/ │ │ │ │ └── resource_apis.yaml │ │ │ ├── screencasts/ │ │ │ │ └── screencasts.yaml │ │ │ ├── sections/ │ │ │ │ └── sections.yaml │ │ │ ├── tags/ │ │ │ │ ├── tag-glide.yaml │ │ │ │ └── tag.yaml │ │ │ ├── tips/ │ │ │ │ └── tips.yaml │ │ │ ├── troubleshooting/ │ │ │ │ └── troubleshooting.yaml │ │ │ ├── ui_components/ │ │ │ │ └── ui_component.yaml │ │ │ ├── variables/ │ │ │ │ └── variables.yaml │ │ │ └── widgets/ │ │ │ └── widgets.yaml │ │ ├── globals/ │ │ │ └── global.yaml │ │ ├── navigation/ │ │ │ ├── docs.yaml │ │ │ ├── extending_docs.yaml │ │ │ └── reference.yaml │ │ └── taxonomies/ │ │ └── categories/ │ │ └── categories.yaml │ ├── css/ │ │ ├── -template.css │ │ ├── README.css │ │ ├── base/ │ │ │ ├── cp.css │ │ │ ├── elements/ │ │ │ │ ├── elements.css │ │ │ │ └── tables.css │ │ │ ├── reset.css │ │ │ └── variables.css │ │ ├── components/ │ │ │ ├── anchors.css │ │ │ ├── buttons.css │ │ │ ├── doc-tabs.css │ │ │ ├── docs-header.css │ │ │ ├── entry-content.css │ │ │ ├── feedback-meerkat.css │ │ │ ├── icon-grid.css │ │ │ ├── imagery/ │ │ │ │ ├── bordered-image.css │ │ │ │ └── full-width-image.css │ │ │ ├── list-turtles.css │ │ │ ├── logos.css │ │ │ ├── nav/ │ │ │ │ ├── back-nav.css │ │ │ │ ├── breadcrumbs.css │ │ │ │ ├── sidebar-advert.css │ │ │ │ ├── sidebar.css │ │ │ │ └── toc.css │ │ │ ├── panel-list.css │ │ │ ├── pill-with-description.css │ │ │ ├── pill.css │ │ │ ├── pro-badge.css │ │ │ ├── promo.css │ │ │ ├── quirky.css │ │ │ ├── related.css │ │ │ ├── search-form.css │ │ │ ├── site-footer.css │ │ │ ├── skip-to-content.css │ │ │ ├── syntax-explainer.css │ │ │ ├── theme-picker.css │ │ │ ├── tiles-with-description.css │ │ │ ├── tip.css │ │ │ ├── version-selector.css │ │ │ └── video.css │ │ ├── objects/ │ │ │ ├── badge-heading.css │ │ │ ├── collapsible-side-menu-with-checkboxes.css │ │ │ ├── entry-content.css │ │ │ ├── scroll-shadows.css │ │ │ └── toc-scroll-spy.css │ │ ├── style.css │ │ ├── torchlight.css │ │ └── utilities/ │ │ ├── animation-keyframes.css │ │ ├── hiders.css │ │ ├── label.css │ │ └── utilities.css │ ├── fieldsets/ │ │ ├── advert_override.yaml │ │ ├── common.yaml │ │ ├── hue_rotate.yaml │ │ ├── navigation.yaml │ │ └── page.yaml │ ├── js/ │ │ ├── anchors.js │ │ ├── color-scheme-preferences.js │ │ ├── cookies.js │ │ ├── dayjs.js │ │ ├── dl.js │ │ ├── docsearch.js │ │ ├── external-links.js │ │ ├── language-badges.js │ │ ├── site.js │ │ ├── tables.js │ │ ├── toc-navigation.js │ │ └── torchlight.js │ ├── sites.yaml │ ├── users/ │ │ ├── groups.yaml │ │ └── roles.yaml │ └── views/ │ ├── default.antlers.html │ ├── deploying.antlers.html │ ├── documentation-search/ │ │ ├── docs/ │ │ │ └── page.antlers.html │ │ ├── extending-docs/ │ │ │ └── page.antlers.html │ │ ├── fieldtypes/ │ │ │ └── fieldtype.antlers.html │ │ ├── modifiers/ │ │ │ └── modifiers.antlers.html │ │ ├── reference/ │ │ │ └── reference.antlers.html │ │ ├── repositories/ │ │ │ └── repositories.antlers.html │ │ ├── screencasts/ │ │ │ └── screencasts.antlers.html │ │ ├── sections/ │ │ │ └── sections.antlers.html │ │ ├── tags/ │ │ │ ├── tag-glide.antlers.html │ │ │ └── tag.antlers.html │ │ ├── tips/ │ │ │ └── tips.antlers.html │ │ ├── troubleshooting/ │ │ │ └── troubleshooting.antlers.html │ │ ├── variables/ │ │ │ └── variables.antlers.html │ │ └── widgets/ │ │ └── widgets.antlers.html │ ├── errors/ │ │ └── 404.antlers.html │ ├── extending/ │ │ └── index.antlers.html │ ├── fieldtypes/ │ │ └── index.antlers.html │ ├── home.antlers.html │ ├── image_dimensions/ │ │ ├── navigation_image.antlers.html │ │ └── tile.antlers.html │ ├── installing.antlers.html │ ├── layout.antlers.html │ ├── listing.antlers.html │ ├── modifiers/ │ │ └── index.antlers.html │ ├── page.antlers.html │ ├── partials/ │ │ ├── details.antlers.html │ │ ├── edit.antlers.html │ │ ├── favicons.antlers.html │ │ ├── footer.antlers.html │ │ ├── head.antlers.html │ │ ├── header.antlers.html │ │ ├── lorem.antlers.html │ │ ├── meta.antlers.html │ │ ├── nav_contents.antlers.html │ │ ├── nav_sidebar.antlers.html │ │ ├── promo.antlers.html │ │ ├── related.antlers.html │ │ ├── scripts.antlers.html │ │ ├── sidebar-promo.antlers.html │ │ ├── site_header.antlers.html │ │ ├── suggest.antlers.html │ │ └── variables.antlers.html │ ├── reference/ │ │ └── index.antlers.html │ ├── repositories/ │ │ └── index.antlers.html │ ├── search.antlers.html │ ├── sitemap.antlers.html │ ├── social.antlers.html │ ├── tabs.antlers.html │ ├── tags/ │ │ ├── glide.antlers.html │ │ └── index.antlers.html │ ├── tips/ │ │ └── index.antlers.html │ ├── troubleshooting/ │ │ └── index.antlers.html │ ├── updates.antlers.html │ └── variables/ │ └── index.antlers.html ├── routes/ │ ├── console.php │ ├── redirects.php │ └── web.php ├── server.php ├── storage/ │ ├── app/ │ │ └── .gitignore │ ├── framework/ │ │ ├── .gitignore │ │ ├── cache/ │ │ │ └── .gitignore │ │ ├── sessions/ │ │ │ └── .gitignore │ │ ├── testing/ │ │ │ └── .gitignore │ │ └── views/ │ │ └── .gitignore │ ├── logs/ │ │ └── .gitignore │ └── statamic/ │ └── .gitignore ├── tests/ │ ├── CreatesApplication.php │ ├── Feature/ │ │ └── ExampleTest.php │ ├── TestCase.php │ └── Unit/ │ └── ExampleTest.php └── vite.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 end_of_line = lf insert_final_newline = true indent_style = space indent_size = 4 trim_trailing_whitespace = true [*.md] trim_trailing_whitespace = false [*.yml] indent_size = 2 ================================================ FILE: .gitattributes ================================================ * text=auto *.css linguist-vendored *.scss linguist-vendored *.js linguist-vendored CHANGELOG.md export-ignore ================================================ FILE: .github/FUNDING.yml ================================================ github: statamic ================================================ FILE: .gitignore ================================================ .DS_Store /node_modules /public/hot /public/storage /public/vendor/statamic /public/build/* /users/* /public/js /public/css/scratch.css /storage/*.key /storage/statamic/users /storage/debugbar/* /storage/glide/* /storage/antlers-language-server/* /storage/stillat/* /vendor .env .env.backup .phpunit.result.cache Homestead.json Homestead.yaml npm-debug.log yarn-error.log # When in dev... /public/**/.meta .antlers.json ================================================ FILE: .nvmrc ================================================ 22 ================================================ FILE: LICENSE.md ================================================ Copyright © Statamic, LLC. Permission is hereby granted to any person obtaining a copy of this software (the “Software”) to use, copy, modify, merge, publish and/or distribute copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 1. **Do not plagiarize.** The above copyright notice and this license shall be included in all copies or substantial portions of the Software. 2. **Do not use the same license on more than one project.** Each licensed copy of the Software shall be actively installed in no more than one production environment at a time. 3. **Do not alter the licensing features.** Software features related to licensing shall not be altered or circumvented in any way, including (but not limited to) license validation, feature or edition restrictions, and update eligibility. 4. **Follow the law.** All use of the Software shall not violate any applicable law or regulation, nor infringe the rights of any other person or entity. Failure to comply with the foregoing conditions will automatically and immediately result in termination of the permission granted hereby. This license does not include any right to receive updates to the Software or technical support. Licensees bear all risk related to the quality and performance of the Software and any modifications made or obtained to it, including liability for actual and consequential harm, such as loss or corruption of data, and any necessary service, repair, or correction. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, INCLUDING SPECIAL, INCIDENTAL AND CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

Statamic Logo

## Statamic Documentation This is the source of the official [Statamic docs][docs]. ## Local Development If you want to work on this project on your local machine, you may follow the instructions below. These instructions assume you are serving the site using [Laravel Valet](https://laravel.com/docs/valet) out of your `~/Sites` directory: 1. Fork this repository 2. Open your terminal and cd to your `~/Sites` folder 3. Clone your fork into the `~/Sites/docs` folder, by running the following command with your username placed into the {username} slot: ``` git clone git@github.com:{username}/docs statamic-docs ``` 4. CD into the new directory you just created. 5. Run the following commands: ``` composer install npm install npm run dev cp .env.example .env php artisan key:generate ``` ## Providing Feedback We love it when people provide thoughtful feedback! Feel free to open issues on for any content you find confusing or incomplete. We are happy to consider anything you feel will make the docs and CMS better. ## Contributing Thank you for considering contributing to Statamic! Every page in the [docs site](https://statamic.dev) has a link at the bottom that will take you right to the exact content file that renders the page. Click the edit button and submit those PRs! **We simply ask that you please review the [contribution guide][contribution] before you send pull requests.** ## Code of Conduct In order to ensure that the Statamic community is welcoming to all and generally a rad place to belong, please review and abide by the [Code of Conduct](https://github.com/statamic/cms/wiki/Code-of-Conduct). ## Important Links - [Statamic Main Site](https://statamic.com) - [Statamic Documentation][docs] - [Statamic CMS Repo][cms-repo] (that we maintain) - [Statamic Application Repo][app-repo] (that you clone) - [Statamic Migrator](https://github.com/statamic/migrator) - [Statamic Discord][discord] [docs]: https://statamic.dev/ [discord]: https://statamic.com/discord [contribution]: https://github.com/statamic/cms/blob/master/CONTRIBUTING.md [app-repo]: https://github.com/statamic/statamic [cms-repo]: https://github.com/statamic/cms ================================================ FILE: app/Http/Controllers/Controller.php ================================================ value('title'), $entry->value('intro'), $entry->value('content'), ])->filter()->implode("\n\n"); }); $markdown = $this->appendMdExtensionToInternalLinks($markdown); return response($markdown, 200, [ 'Content-Type' => 'text/markdown; charset=UTF-8', ]); } private function appendMdExtensionToInternalLinks(string $markdown): string { return preg_replace_callback( '/(?shouldAppendMdExtension($url)) { $url .= '.md'; } return "[$text]($url)"; }, $markdown ); } private function shouldAppendMdExtension(string $url): bool { if (preg_match('/^https?:\/\//', $url)) { return false; } if (preg_match('/\.[a-z0-9]{2,4}$/i', $url)) { return false; } return true; } } ================================================ FILE: app/Http/Controllers/LlmsTxtController.php ================================================ structure()->trees()->first()->tree(); $lines = ['# Statamic Documentation', '']; foreach ($tree as $section) { $children = $section['children'] ?? []; if (! $children) { continue; } $sectionEntry = Entry::find($section['entry']); $lines[] = '## '.$sectionEntry->value('title'); $firstChild = Entry::find($children[0]['entry']); if ($firstChild && str_contains($firstChild->slug(), 'overview')) { if ($intro = $firstChild->value('intro')) { $lines[] = '> '.str_replace("\n", ' ', $intro); } } $lines[] = ''; foreach ($children as $child) { $entry = Entry::find($child['entry']); if (! $entry) { continue; } $url = $entry->url(); if (! $url) { continue; } $title = $entry->value('title'); $isExternal = str_starts_with($url, 'http'); $href = $isExternal ? $url : url($url).'.md'; $line = '- ['.$title.']('.$href.')'; if ($intro = $entry->value('intro')) { $line .= ': '.str_replace("\n", ' ', $intro); } $lines[] = $line; } $lines[] = ''; } return $lines; }); return response(implode("\n", $lines), 200, [ 'Content-Type' => 'text/plain; charset=UTF-8', ]); } } ================================================ FILE: app/Http/Middleware/CheckForMaintenanceMode.php ================================================ getHeaderWords(); if (count($words) > 1) { array_shift($words); return implode(' ', $words); } if ($words[0] === 'tip') { return 'Hot Tip!'; } if ($words[0] === 'hint') { return 'Hinty Hint!'; } if ($words[0] === 'warning') { return 'Warning!'; } if ($words[0] === 'best-practice') { return 'Best Practice'; } return null; } public function getType(): ?string { $words = $this->getHeaderWords(); if (count($words) > 0) { return $words[0]; } return null; } public function getHeaderWords(): array { return \preg_split('/\s+/', $this->header ?? '') ?: []; } public function setHeader($header) { $this->header = $header; } public function setLiteral(string $literal): void { $this->literal = $literal; } public function getLiteral(): string { return $this->literal; } } ================================================ FILE: app/Markdown/Hint/HintExtension.php ================================================ addBlockStartParser(new HintStartParser); $environment->addRenderer(Hint::class, new HintRenderer); } } ================================================ FILE: app/Markdown/Hint/HintParser.php ================================================ block = new Hint; $this->block->setHeader($headerText); } public function getBlock(): Hint { return $this->block; } public function isContainer(): bool { return true; } public function canContain(AbstractBlock $childBlock): bool { return true; } public function canHaveLazyContinuationLines(): bool { return false; } public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue { if ($cursor->getLine() === ':::') { return BlockContinue::finished(); } return BlockContinue::at($cursor); } } ================================================ FILE: app/Markdown/Hint/HintRenderer.php ================================================ data->get('attributes'); isset($attrs['class']) ? $attrs['class'] .= ' hint' : $attrs['class'] = 'c-tip'; if ($type = $node->getType()) { $attrs['class'] = isset($attrs['class']) ? $attrs['class'].' ' : ''; $attrs['class'] .= $type.' c-tip--'.$type; } if ($type === 'watch') { return $this->renderWatch($node, $childRenderer, $attrs); } $title = $node->getTitle(); $title = $title ? new HtmlElement( 'span', ['class' => 'hint-title'], $title, ) : ''; $content = $childRenderer->renderNodes($node->children()); // Add mascot image for tips and best practices $mascot = in_array($type, ['tip', 'hint', 'best-practice', 'warning']) ? 'A troll pointing a teaching stick' : ''; return new HtmlElement( 'div', $attrs, "\n". $title."\n". $content. $mascot. "\n" ); } private function renderWatch(Hint $node, ChildNodeRendererInterface $childRenderer, array $attrs) { // Strip all HTML tags except for essential formatting elements (links, code, bold, italic) // This ensures the caption is rendered as plain text without unwanted p tags or other wrappers $caption = strip_tags($childRenderer->renderNodes($node->children()), ''); return new HtmlElement( 'div', $attrs, '
'. ''. '
'.$caption.'
'. '
' ); } } ================================================ FILE: app/Markdown/Hint/HintStartParser.php ================================================ isIndented()) { return BlockStart::none(); } $fence = $cursor->match('/^(?:\:{3,}\s?(?!.*`))/'); if ($fence === null) { return BlockStart::none(); } $headerText = $cursor->getRemainder(); $cursor->advanceToEnd(); return BlockStart::of(new HintParser($headerText))->at($cursor); } } ================================================ FILE: app/Markdown/Tabs/TabbedCodeBlock.php ================================================ codeSamples[$language] = $code; } public function getCodeSamples() { return $this->codeSamples; } } ================================================ FILE: app/Markdown/Tabs/TabbedCodeBlockExtension.php ================================================ addBlockStartParser(new TabbedCodeStartParser); $environment->addRenderer(TabbedCodeBlock::class, new TabsRenderer); } } ================================================ FILE: app/Markdown/Tabs/TabbedCodeStartParser.php ================================================ isIndented()) { return BlockStart::none(); } $fence = $cursor->match('/^(?:\:{2,}tabs)/'); if ($fence === null) { return BlockStart::none(); } $headerText = $cursor->getRemainder(); $cursor->advanceToEnd(); return BlockStart::of(new TabsParser)->at($cursor); } } ================================================ FILE: app/Markdown/Tabs/TabsParser.php ================================================ tabs = new TabbedCodeBlock; } public function isContainer(): bool { return true; } public function canContain(AbstractBlock $childBlock): bool { return true; } public function getBlock(): AbstractBlock { return $this->tabs; } public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue { if ($cursor->getLine() === '::') { return BlockContinue::finished(); } return BlockContinue::at($cursor); } } ================================================ FILE: app/Markdown/Tabs/TabsRenderer.php ================================================ 'Blade', 'antlers' => 'Antlers', 'php' => 'PHP', ]; public function render(Node $node, ChildNodeRendererInterface $childRenderer) { TabbedCodeBlock::assertInstanceOf($node); $attrs = $node->data->get('attributes'); $attrs['class'] = 'c-doc-tabs'; $tabs = []; $currentTab = []; $tabNameSetManually = false; $lastTabName = ''; foreach ($node->children() as $child) { if ($child instanceof FencedCode && ! $tabNameSetManually) { $lastTabName = mb_strtoupper($child->getInfo()); } if ($child instanceof Paragraph && $child->firstChild() instanceof Text) { /** @var Text $text */ $text = $child->firstChild(); if (Str::startsWith($text->getLiteral(), '::tab')) { $renderedContent = $childRenderer->renderNodes($currentTab); $currentTab = []; $extra = trim(mb_substr($text->getLiteral(), 5)); $currentTabName = $lastTabName; if (mb_strlen($extra) > 0) { $lastTabName = $extra; $tabNameSetManually = true; } else { $tabNameSetManually = false; } if (mb_strlen(strip_tags($renderedContent)) == 0) { continue; } $tabs[$currentTabName] = $renderedContent; continue; } } $currentTab[] = $child; } if (count($currentTab)) { $tabs[$lastTabName] = $childRenderer->renderNodes($currentTab); $currentTab = []; } $sampleNames = []; foreach ($tabs as $language => $sample) { if (array_key_exists($language, $this->languageNames)) { $sampleNames[$language] = $this->languageNames[$language]; continue; } $sampleNames[$language] = mb_strtoupper($language); } return new HtmlElement( 'div', $attrs, view('tabs', ['tabs' => $sampleNames, 'samples' => $tabs, 'active' => array_keys($tabs)[0]])->render() ); } } ================================================ FILE: app/Models/User.php ================================================ */ use HasFactory, Notifiable; /** * Get the attributes that should be cast. * * @return array */ protected function casts(): array { return [ 'email_verified_at' => 'datetime', 'password' => 'hashed', ]; } } ================================================ FILE: app/Modifiers/Split.php ================================================ split($size) ->map(function ($collection) { return [ 'items' => $collection->all(), ]; })->all(); } } ================================================ FILE: app/Modifiers/Toc.php ================================================ context = $context; $creatingIds = Arr::get($params, 0) == 'ids'; // Here maxHeadingLevels is set to either 5 (when creating IDs) or 3 (for TOC) [$toc, $content] = $this->create($value, $creatingIds ? 5 : 3); return $creatingIds ? $content : $toc; } // Good golly this thing is ugly. private function create($content, $maxHeadingLevels) { // First try with h2-hN headings preg_match_all('/]*)>(.*)<\/h[2-'.$maxHeadingLevels.']>/i', $content, $matches, PREG_SET_ORDER); // If we don't have enough entries, include h1 headings as well if (count($matches) < 3) { preg_match_all('/]*)>(.*)<\/h[1-'.$maxHeadingLevels.']>/i', $content, $matches, PREG_SET_ORDER); } if (! $matches) { return [null, $content]; } // Track unique anchor IDs across the document global $anchors; $anchors = []; // Initialize TOC with an unordered list $toc = '"; $lvl--; } $toc .= ''."\n"; $toc .= ''."\n"; return [$toc, $content]; } /** * Safely extracts value from Statamic Value objects */ private function valueGet($value) { if ($value instanceof \Statamic\Fields\Value) { return $value->value(); } return $value; } private function slugify($text) { $slugified = Statamic::modify($text)->replace('&', '')->slugify()->stripTags(); // Remove 'code-code' from the slugified text e.g. Otherwise "the `@` ignore symbol" gets converted to `the-code-code-ignore-symbol` $slugified = str_replace('code-code-', '', $slugified); // Remove HTML entity remnants that might be left after processing // Only remove these if they appear as standalone words or at word boundaries $slugified = preg_replace('/\b(codelt|rt|gtcode)\b/', '', $slugified); return $slugified; } } ================================================ FILE: app/Providers/AppServiceProvider.php ================================================ runningConsoleCommand('search:update')) { TorchlightOptions::setDefaultOptionsBuilder(fn () => TorchlightOptions::fromArray(config('torchlight.options'))); $extension = new TorchlightExtension(config('torchlight.theme')); $extension ->renderer() ->setDefaultGrammar(config('torchlight.options.defaultLanguage')); Markdown::addExtension(fn () => $extension); } Event::listen(SearchEntriesCreated::class, SearchEntriesCreatedListener::class); StorybookSearchProvider::register(); } } ================================================ FILE: app/Search/DocTransformer.php ================================================ lower() ->explode(' ') ->map(fn ($word) => Str::singular($word)) ->join(' '); } public function handle(DocumentFragment $fragment, $entry): void { $fragment->additionalContextData[] = $this->normalizeConent($entry->title); // Add some extra details to "additional_context" if (Str::containsAll($fragment->content, ['clear', 'cache'])) { $fragment->additionalContextData[] = 'delete cache'; } if (Str::contains($fragment->content, 'JS Drivers')) { $fragment->additionalContextData[] = 'javascript drivers'; } } } ================================================ FILE: app/Search/Listeners/SearchEntriesCreatedListener.php ================================================ level; for ($i = array_search($target, $headers) - 1; $i >= 0; $i--) { $header = $headers[$i]; if ($header->level < $currentLevel) { $hierarchy[] = $header; $currentLevel = $header->level; } } foreach (array_reverse($hierarchy) as $level) { $levels[$level->level] = str($level->text)->replaceEnd('#', '')->__toString(); } return $levels; } /** * Handle the event. */ public function handle(SearchEntriesCreated $event): void { $collection = $event->entry->collection()->title; $headers = []; foreach ($event->sections as $section) { if ($section->fragment->headerDetails == null) { continue; } $headers[] = $section->fragment->headerDetails; } foreach ($event->sections as $section) { $data = $section->searchEntry->data(); $data['search_title'] = str($data['search_title'] ?? '')->replaceEnd('#', '')->__toString(); $category = match (true) { $collection === 'Pages' => ($event->entry->parent() ? $event->entry->parent()?->title.' » ' : null).$data['origin_title'], default => $collection.' » '.$data['origin_title'], }; $parentHeadings = null; if ( $section->fragment->headerDetails != null && $section->fragment->headerDetails->level >= 3 ) { $header = $section->fragment->headerDetails; $parentHeadings = $this->getParentHeadings( $headers, $header ); $parentHeadings[$header->level] = $header->text; } if ($parentHeadings === null) { $parentHeadings = []; if ($data['search_title'] != null && $data['origin_title'] != $data['search_title']) { $parentHeadings[1] = $data['search_title']; } } if (count($parentHeadings) > 2) { array_shift($parentHeadings); } $data['hierarchy_lvl0'] = $category; $data['hierarchy_lvl1'] = str(implode(' » ', $parentHeadings))->replaceEnd('#', '')->__toString(); if ($data['is_root']) { $data['content'] = strip_tags($event->entry->intro ?? $event->entry->description ?? $data['search_content']); } else { $data['content'] = $data['search_content'] ?? ''; } $data['url'] = $data['search_url']; foreach ($this->escapeProperties as $property) { if (! $data->has($property)) { continue; } $data[$property] = e($data[$property]); } // Clear this out to prevent "too much" from a specific page dominating the results. if (! $data['is_root']) { $data['origin_title'] = null; } $section->searchEntry->data($data); } } } ================================================ FILE: app/Search/RequestContentRetriever.php ================================================ instance('request', $request); Cascade::withRequest($request); }); $content = ''; try { $content = $entry->toResponse($request)->getContent(); } finally { app()->instance('request', $originalRequest); } return $this->extractArticleContent($content); } protected function extractArticleContent(string $content): string { $dom = new DOMDocument; libxml_use_internal_errors(true); $dom->loadHTML($content); libxml_clear_errors(); $articles = $dom->getElementsByTagName('article'); $result = ''; foreach ($articles as $article) { $result .= $dom->saveHTML($article); } return $result; } } ================================================ FILE: app/Search/Storybook/StorybookSearchProvider.php ================================================ collect('entries') ->filter(fn (array $story) => Str::endsWith($story['id'], ['--docs', '--default'])) ->unique('title') ->map(fn (array $story) => StorybookSearchable::from($story)) ->map->reference(); } public function contains($searchable): bool { // TODO: Implement contains() method. } public function find(array $keys): Collection { $stories = Http::get('https://ui.statamic.dev/index.json')->collect('entries'); return collect($keys)->map(fn (string $key) => StorybookSearchable::from($stories->get($key))); } } ================================================ FILE: app/Search/Storybook/StorybookSearchable.php ================================================ id = $component['id']; $instance->title = Str::after($component['title'], '/'); return $instance; } public function id(): string { return $this->id; } public function title(): string { return $this->title; } public function getSearchValue(string $field) { if ($field === 'title' || $field === 'origin_title' || $field === 'search_title') { return $this->title(); } if ($field === 'hierarchy_lvl0') { return "UI Components » {$this->title()}"; } if ($field === 'url') { return $this->url(); } return null; } public function url(): string { return "https://ui.statamic.dev/?path=/docs/{$this->id}"; } public function reference(): string { return "storybook::{$this->id()}"; } } ================================================ FILE: app/Tags/GithubCommitsUrl.php ================================================ context->get('id')) { return false; } $content = Data::find($id); if ($content instanceof \Statamic\Taxonomies\LocalizedTerm) { return; } $path = Str::after($content->path(), 'content/'); return "https://github.com/statamic/docs/commits/{$this->getCurrentBranch()}/content/{$path}"; } private function getCurrentBranch(): string { return collect(config('docs.versions')) ->where('version', config('docs.version')) ->first()['branch'] ?? '5.x'; } } ================================================ FILE: app/Tags/GithubEditUrl.php ================================================ context->get('id')) { return false; } $content = Data::find($id); if ($content instanceof \Statamic\Taxonomies\LocalizedTerm) { return; } $path = Str::after($path = $content->path(), 'content/'); return "https://github.com/statamic/docs/blob/{$this->getCurrentBranch()}/content/{$path}"; } private function getCurrentBranch(): string { return collect(config('docs.versions')) ->where('version', config('docs.version')) ->first()['branch'] ?? '5.x'; } } ================================================ FILE: app/Tags/HeroSponsors.php ================================================ addHour(), function () { try { return $this->sponsors()->collect()->where('price', '>=', 25); } catch (\Exception $e) { Log::error($e); return collect(['error' => true]); } }); } private function sponsors() { return LazyCollection::make(function () { $cursor = null; do { $data = $this->request($cursor); $sponsorships = Arr::get($data, 'data.viewer.organization.sponsorshipsAsMaintainer'); $cursor = $sponsorships['pageInfo']['endCursor']; $hasNextPage = $sponsorships['pageInfo']['hasNextPage']; yield from collect($sponsorships['nodes'])->map(fn ($sponsorship) => array_merge( $sponsorship['sponsorEntity'], ['price' => $sponsorship['tier']['monthlyPriceInDollars']] )); } while ($hasNextPage && $cursor); }); } private function request($cursor) { return Http::baseUrl('https://api.github.com') ->withToken(config('services.github.token')) ->asJson() ->accept('application/vnd.github.v4+json') ->withUserAgent('statamic/docs') ->post('/graphql', [ 'query' => $this->query(), 'variables' => ['cursor' => $cursor], ]) ->json(); } private function query() { return <<<'GQL' query($cursor: String) { viewer { organization(login: "statamic") { sponsorshipsAsMaintainer(first: 100, after: $cursor, orderBy: {direction: ASC, field: CREATED_AT}) { pageInfo { hasNextPage endCursor } nodes { sponsorEntity { ... on User { name url avatarUrl(size: 80) } ... on Organization { name url avatarUrl(size: 80) } } tier { name monthlyPriceInDollars } } } } } } GQL; } } ================================================ FILE: app/ViewModels/Fieldtypes.php ================================================ ucwords($this->cascade->get('title')).' Fieldtype']; } } ================================================ FILE: app/ViewModels/Modifiers.php ================================================ ucwords($this->cascade->get('title')).' Modifier']; } } ================================================ FILE: app/ViewModels/Tags.php ================================================ ucwords($this->cascade->get('slug')).' Tag']; } } ================================================ FILE: app/ViewModels/Variables.php ================================================ strtolower($this->cascade->get('slug'))]; } } ================================================ FILE: artisan ================================================ #!/usr/bin/env php handleCommand(new ArgvInput); exit($status); ================================================ FILE: bootstrap/app.php ================================================ withRouting( web: __DIR__.'/../routes/web.php', commands: __DIR__.'/../routes/console.php', health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { // }) ->withExceptions(function (Exceptions $exceptions): void { // })->create(); ================================================ FILE: bootstrap/cache/.gitignore ================================================ * !.gitignore ================================================ FILE: bootstrap/providers.php ================================================ env('APP_NAME', 'Statamic'), /* |-------------------------------------------------------------------------- | Application Environment |-------------------------------------------------------------------------- | | This value determines the "environment" your application is currently | running in. This may determine how you prefer to configure various | services the application utilizes. Set this in your ".env" file. | */ 'env' => env('APP_ENV', 'production'), /* |-------------------------------------------------------------------------- | Application Debug Mode |-------------------------------------------------------------------------- | | When your application is in debug mode, detailed error messages with | stack traces will be shown on every error that occurs within your | application. If disabled, a simple generic error page is shown. | */ 'debug' => (bool) env('APP_DEBUG', false), /* |-------------------------------------------------------------------------- | Application URL |-------------------------------------------------------------------------- | | This URL is used by the console to properly generate URLs when using | the Artisan command line tool. You should set this to the root of | the application so that it's available within Artisan commands. | */ 'url' => env('APP_URL', 'http://localhost'), /* |-------------------------------------------------------------------------- | Application Timezone |-------------------------------------------------------------------------- | | Here you may specify the default timezone for your application, which | will be used by the PHP date and date-time functions. The timezone | is set to "UTC" by default as it is suitable for most use cases. | */ 'timezone' => 'UTC', /* |-------------------------------------------------------------------------- | Application Locale Configuration |-------------------------------------------------------------------------- | | The application locale determines the default locale that will be used | by Laravel's translation / localization methods. This option can be | set to any locale for which you plan to have translation strings. | */ 'locale' => env('APP_LOCALE', 'en'), 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'), 'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'), /* |-------------------------------------------------------------------------- | Encryption Key |-------------------------------------------------------------------------- | | This key is utilized by Laravel's encryption services and should be set | to a random, 32 character string to ensure that all encrypted values | are secure. You should do this prior to deploying the application. | */ 'cipher' => 'AES-256-CBC', 'key' => env('APP_KEY'), 'previous_keys' => [ ...array_filter( explode(',', (string) env('APP_PREVIOUS_KEYS', '')) ), ], /* |-------------------------------------------------------------------------- | Maintenance Mode Driver |-------------------------------------------------------------------------- | | These configuration options determine the driver used to determine and | manage Laravel's "maintenance mode" status. The "cache" driver will | allow maintenance mode to be controlled across multiple machines. | | Supported drivers: "file", "cache" | */ 'maintenance' => [ 'driver' => env('APP_MAINTENANCE_DRIVER', 'file'), 'store' => env('APP_MAINTENANCE_STORE', 'file'), ], ]; ================================================ FILE: config/auth.php ================================================ [ 'guard' => env('AUTH_GUARD', 'web'), 'passwords' => env('AUTH_PASSWORD_BROKER', 'users'), ], /* |-------------------------------------------------------------------------- | Authentication Guards |-------------------------------------------------------------------------- | | Next, you may define every authentication guard for your application. | Of course, a great default configuration has been defined for you | which utilizes session storage plus the Eloquent user provider. | | All authentication guards have a user provider, which defines how the | users are actually retrieved out of your database or other storage | system used by the application. Typically, Eloquent is utilized. | | Supported: "session" | */ 'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], ], /* |-------------------------------------------------------------------------- | User Providers |-------------------------------------------------------------------------- | | All authentication guards have a user provider, which defines how the | users are actually retrieved out of your database or other storage | system used by the application. Typically, Eloquent is utilized. | | If you have multiple user tables or models you may configure multiple | providers to represent the model / table. These providers may then | be assigned to any extra authentication guards you have defined. | | Supported: "statamic", "database", "eloquent" | */ 'providers' => [ 'users' => [ 'driver' => 'statamic', ], // 'users' => [ // 'driver' => 'eloquent', // 'model' => env('AUTH_MODEL', User::class), // ], // 'users' => [ // 'driver' => 'database', // 'table' => 'users', // ], ], /* |-------------------------------------------------------------------------- | Resetting Passwords |-------------------------------------------------------------------------- | | These configuration options specify the behavior of Laravel's password | reset functionality, including the table utilized for token storage | and the user provider that is invoked to actually retrieve users. | | The expiry time is the number of minutes that each reset token will be | considered valid. This security feature keeps tokens short-lived so | they have less time to be guessed. You may change this as needed. | | The throttle setting is the number of seconds a user must wait before | generating more password reset tokens. This prevents the user from | quickly generating a very large amount of password reset tokens. | */ 'passwords' => [ 'users' => [ 'provider' => 'users', 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'), 'expire' => 60, 'throttle' => 60, ], 'activations' => [ 'provider' => 'users', 'table' => env('AUTH_ACTIVATION_TOKEN_TABLE', 'password_activation_tokens'), 'expire' => 4320, 'throttle' => 60, ], ], /* |-------------------------------------------------------------------------- | Password Confirmation Timeout |-------------------------------------------------------------------------- | | Here you may define the number of seconds before a password confirmation | window expires and users are asked to re-enter their password via the | confirmation screen. By default, the timeout lasts for three hours. | */ 'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800), ]; ================================================ FILE: config/cache.php ================================================ env('CACHE_STORE', 'file'), /* |-------------------------------------------------------------------------- | Cache Stores |-------------------------------------------------------------------------- | | Here you may define all of the cache "stores" for your application as | well as their drivers. You may even define multiple stores for the | same cache driver to group types of items stored in your caches. | | Supported drivers: "array", "database", "file", "memcached", | "redis", "dynamodb", "octane", | "failover", "null" | */ 'stores' => [ 'array' => [ 'driver' => 'array', 'serialize' => false, ], 'database' => [ 'driver' => 'database', 'connection' => env('DB_CACHE_CONNECTION', null), 'table' => env('DB_CACHE_TABLE', 'cache'), 'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'), 'lock_table' => env('DB_CACHE_LOCK_TABLE'), ], 'file' => [ 'driver' => 'file', 'path' => storage_path('framework/cache/data'), 'lock_path' => storage_path('framework/cache/data'), ], 'memcached' => [ 'driver' => 'memcached', 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), 'sasl' => [ env('MEMCACHED_USERNAME'), env('MEMCACHED_PASSWORD'), ], 'options' => [ // Memcached::OPT_CONNECT_TIMEOUT => 2000, ], 'servers' => [ [ 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 'port' => env('MEMCACHED_PORT', 11211), 'weight' => 100, ], ], ], 'redis' => [ 'driver' => 'redis', 'connection' => env('REDIS_CACHE_CONNECTION', 'cache'), 'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'), ], 'dynamodb' => [ 'driver' => 'dynamodb', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), 'endpoint' => env('DYNAMODB_ENDPOINT'), ], 'octane' => [ 'driver' => 'octane', ], 'failover' => [ 'driver' => 'failover', 'stores' => [ 'database', 'array', ], ], 'static_cache' => [ 'driver' => 'file', 'path' => storage_path('statamic/static-urls-cache'), ], ], /* |-------------------------------------------------------------------------- | Cache Key Prefix |-------------------------------------------------------------------------- | | When utilizing the APC, database, memcached, Redis, and DynamoDB cache | stores, there might be other applications using the same cache. For | that reason, you may prefix every cache key to avoid collisions. | */ 'prefix' => env('CACHE_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-cache-'), /* |-------------------------------------------------------------------------- | Serializable Classes |-------------------------------------------------------------------------- | | This value determines the classes that can be unserialized from cache | storage. By default, no PHP classes will be unserialized from your | cache to prevent gadget chain attacks if your APP_KEY is leaked. | */ 'serializable_classes' => false, ]; ================================================ FILE: config/database.php ================================================ env('DB_CONNECTION', 'sqlite'), /* |-------------------------------------------------------------------------- | Database Connections |-------------------------------------------------------------------------- | | Below are all of the database connections defined for your application. | An example configuration is provided for each database system which | is supported by Laravel. You're free to add / remove connections. | */ 'connections' => [ 'sqlite' => [ 'driver' => 'sqlite', 'url' => env('DB_URL'), 'database' => env('DB_DATABASE', database_path('database.sqlite')), 'prefix' => '', 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), 'busy_timeout' => null, 'journal_mode' => null, 'synchronous' => null, 'transaction_mode' => 'DEFERRED', ], 'mysql' => [ 'driver' => 'mysql', 'url' => env('DB_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE', 'laravel'), 'username' => env('DB_USERNAME', 'root'), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => env('DB_CHARSET', 'utf8mb4'), 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), 'prefix' => '', 'prefix_indexes' => true, 'strict' => true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ (PHP_VERSION_ID >= 80500 ? Mysql::ATTR_SSL_CA : PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'), ]) : [], ], 'mariadb' => [ 'driver' => 'mariadb', 'url' => env('DB_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE', 'laravel'), 'username' => env('DB_USERNAME', 'root'), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => env('DB_CHARSET', 'utf8mb4'), 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), 'prefix' => '', 'prefix_indexes' => true, 'strict' => true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ (PHP_VERSION_ID >= 80500 ? Mysql::ATTR_SSL_CA : PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'), ]) : [], ], 'pgsql' => [ 'driver' => 'pgsql', 'url' => env('DB_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '5432'), 'database' => env('DB_DATABASE', 'laravel'), 'username' => env('DB_USERNAME', 'root'), 'password' => env('DB_PASSWORD', ''), 'charset' => env('DB_CHARSET', 'utf8'), 'prefix' => '', 'prefix_indexes' => true, 'search_path' => 'public', 'sslmode' => env('DB_SSLMODE', 'prefer'), ], 'sqlsrv' => [ 'driver' => 'sqlsrv', 'url' => env('DB_URL'), 'host' => env('DB_HOST', 'localhost'), 'port' => env('DB_PORT', '1433'), 'database' => env('DB_DATABASE', 'laravel'), 'username' => env('DB_USERNAME', 'root'), 'password' => env('DB_PASSWORD', ''), 'charset' => env('DB_CHARSET', 'utf8'), 'prefix' => '', 'prefix_indexes' => true, // 'encrypt' => env('DB_ENCRYPT', 'yes'), // 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'), ], ], /* |-------------------------------------------------------------------------- | Migration Repository Table |-------------------------------------------------------------------------- | | This table keeps track of all the migrations that have already run for | your application. Using this information, we can determine which of | the migrations on disk haven't actually been run on the database. | */ 'migrations' => [ 'table' => 'migrations', 'update_date_on_publish' => true, ], /* |-------------------------------------------------------------------------- | Redis Databases |-------------------------------------------------------------------------- | | Redis is an open source, fast, and advanced key-value store that also | provides a richer body of commands than a typical key-value system | such as Memcached. You may define your connection settings here. | */ 'redis' => [ 'client' => env('REDIS_CLIENT', 'phpredis'), 'options' => [ 'cluster' => env('REDIS_CLUSTER', 'redis'), 'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-database-'), 'persistent' => env('REDIS_PERSISTENT', false), ], 'default' => [ 'url' => env('REDIS_URL'), 'host' => env('REDIS_HOST', '127.0.0.1'), 'username' => env('REDIS_USERNAME'), 'password' => env('REDIS_PASSWORD'), 'port' => env('REDIS_PORT', '6379'), 'database' => env('REDIS_DB', '0'), 'max_retries' => env('REDIS_MAX_RETRIES', 3), 'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'), 'backoff_base' => env('REDIS_BACKOFF_BASE', 100), 'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000), ], 'cache' => [ 'url' => env('REDIS_URL'), 'host' => env('REDIS_HOST', '127.0.0.1'), 'username' => env('REDIS_USERNAME'), 'password' => env('REDIS_PASSWORD'), 'port' => env('REDIS_PORT', '6379'), 'database' => env('REDIS_CACHE_DB', '1'), 'max_retries' => env('REDIS_MAX_RETRIES', 3), 'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'), 'backoff_base' => env('REDIS_BACKOFF_BASE', 100), 'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000), ], ], ]; ================================================ FILE: config/docs.php ================================================ env('STATAMIC_DOCS_VERSION', '6'), 'versions' => [ [ 'version' => '6', 'branch' => '6.x', 'url' => 'https://statamic.dev', ], [ 'version' => '5', 'branch' => '5.x', 'url' => 'https://v5.statamic.dev', ], ], ]; ================================================ FILE: config/filesystems.php ================================================ env('FILESYSTEM_DISK', 'local'), /* |-------------------------------------------------------------------------- | Filesystem Disks |-------------------------------------------------------------------------- | | Below you may configure as many filesystem disks as necessary, and you | may even configure multiple disks for the same driver. Examples for | most supported storage drivers are configured here for reference. | | Supported drivers: "local", "ftp", "sftp", "s3" | */ 'disks' => [ 'local' => [ 'driver' => 'local', 'root' => storage_path('app/private'), 'serve' => true, 'throw' => false, 'report' => false, ], 'public' => [ 'driver' => 'local', 'root' => storage_path('app/public'), 'url' => rtrim(env('APP_URL', 'http://localhost'), '/').'/storage', 'visibility' => 'public', 'throw' => false, 'report' => false, ], 's3' => [ 'driver' => 's3', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION'), 'bucket' => env('AWS_BUCKET'), 'url' => env('AWS_URL'), 'endpoint' => env('AWS_ENDPOINT'), 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), 'throw' => false, 'report' => false, // 'visibility' => 'public', // https://statamic.dev/assets#container-visibility ], 'assets' => [ 'driver' => 'local', 'root' => public_path('img'), 'url' => '/img', 'visibility' => 'public', 'throw' => false, 'report' => false, ], ], /* |-------------------------------------------------------------------------- | Symbolic Links |-------------------------------------------------------------------------- | | Here you may configure the symbolic links that will be created when the | `storage:link` Artisan command is executed. The array keys should be | the locations of the links and the values should be their targets. | */ 'links' => [ public_path('storage') => storage_path('app/public'), ], ]; ================================================ FILE: config/logging.php ================================================ env('LOG_CHANNEL', 'stack'), /* |-------------------------------------------------------------------------- | Deprecations Log Channel |-------------------------------------------------------------------------- | | This option controls the log channel that should be used to log warnings | regarding deprecated PHP and library features. This allows you to get | your application ready for upcoming major versions of dependencies. | */ 'deprecations' => [ 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), 'trace' => env('LOG_DEPRECATIONS_TRACE', false), ], /* |-------------------------------------------------------------------------- | Log Channels |-------------------------------------------------------------------------- | | Here you may configure the log channels for your application. Laravel | utilizes the Monolog PHP logging library, which includes a variety | of powerful log handlers and formatters that you're free to use. | | Available drivers: "single", "daily", "slack", "syslog", | "errorlog", "monolog", "custom", "stack" | */ 'channels' => [ 'stack' => [ 'driver' => 'stack', 'channels' => explode(',', (string) env('LOG_STACK', 'single')), 'ignore_exceptions' => false, ], 'single' => [ 'driver' => 'single', 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), 'replace_placeholders' => true, ], 'daily' => [ 'driver' => 'daily', 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), 'days' => env('LOG_DAILY_DAYS', 14), 'replace_placeholders' => true, ], 'slack' => [ 'driver' => 'slack', 'url' => env('LOG_SLACK_WEBHOOK_URL'), 'username' => env('LOG_SLACK_USERNAME', env('APP_NAME', 'Laravel')), 'emoji' => env('LOG_SLACK_EMOJI', ':boom:'), 'level' => env('LOG_LEVEL', 'critical'), 'replace_placeholders' => true, ], 'papertrail' => [ 'driver' => 'monolog', 'level' => env('LOG_LEVEL', 'debug'), 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class), 'handler_with' => [ 'host' => env('PAPERTRAIL_URL'), 'port' => env('PAPERTRAIL_PORT'), 'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'), ], 'processors' => [PsrLogMessageProcessor::class], ], 'stderr' => [ 'driver' => 'monolog', 'level' => env('LOG_LEVEL', 'debug'), 'handler' => StreamHandler::class, 'handler_with' => [ 'stream' => 'php://stderr', ], 'formatter' => env('LOG_STDERR_FORMATTER'), 'processors' => [PsrLogMessageProcessor::class], ], 'syslog' => [ 'driver' => 'syslog', 'level' => env('LOG_LEVEL', 'debug'), 'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER), 'replace_placeholders' => true, ], 'errorlog' => [ 'driver' => 'errorlog', 'level' => env('LOG_LEVEL', 'debug'), 'replace_placeholders' => true, ], 'null' => [ 'driver' => 'monolog', 'handler' => NullHandler::class, ], 'emergency' => [ 'path' => storage_path('logs/laravel.log'), ], ], ]; ================================================ FILE: config/mail.php ================================================ env('MAIL_MAILER', 'log'), /* |-------------------------------------------------------------------------- | Mailer Configurations |-------------------------------------------------------------------------- | | Here you may configure all of the mailers used by your application plus | their respective settings. Several examples have been configured for | you and you are free to add your own as your application requires. | | Laravel supports a variety of mail "transport" drivers that can be used | when delivering an email. You may specify which one you're using for | your mailers below. You may also add additional mailers if needed. | | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2", | "postmark", "resend", "log", "array", | "failover", "roundrobin" | */ 'mailers' => [ 'smtp' => [ 'transport' => 'smtp', 'scheme' => env('MAIL_SCHEME'), 'url' => env('MAIL_URL'), 'host' => env('MAIL_HOST', '127.0.0.1'), 'port' => env('MAIL_PORT', 2525), 'username' => env('MAIL_USERNAME'), 'password' => env('MAIL_PASSWORD'), 'timeout' => null, 'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url((string) env('APP_URL', 'http://localhost'), PHP_URL_HOST)), ], 'ses' => [ 'transport' => 'ses', ], 'postmark' => [ 'transport' => 'postmark', // 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'), // 'client' => [ // 'timeout' => 5, // ], ], 'resend' => [ 'transport' => 'resend', ], 'sendmail' => [ 'transport' => 'sendmail', 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'), ], 'log' => [ 'transport' => 'log', 'channel' => env('MAIL_LOG_CHANNEL'), ], 'array' => [ 'transport' => 'array', ], 'failover' => [ 'transport' => 'failover', 'mailers' => [ 'smtp', 'log', ], 'retry_after' => 60, ], 'roundrobin' => [ 'transport' => 'roundrobin', 'mailers' => [ 'ses', 'postmark', ], 'retry_after' => 60, ], ], /* |-------------------------------------------------------------------------- | Global "From" Address |-------------------------------------------------------------------------- | | You may wish for all emails sent by your application to be sent from | the same address. Here you may specify a name and address that is | used globally for all emails that are sent by your application. | */ 'from' => [ 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), 'name' => env('MAIL_FROM_NAME', env('APP_NAME', 'Laravel')), ], ]; ================================================ FILE: config/queue.php ================================================ env('QUEUE_CONNECTION', 'sync'), /* |-------------------------------------------------------------------------- | Queue Connections |-------------------------------------------------------------------------- | | Here you may configure the connection options for every queue backend | used by your application. An example configuration is provided for | each backend supported by Laravel. You're also free to add more. | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", | "deferred", "background", "failover", "null" | */ 'connections' => [ 'sync' => [ 'driver' => 'sync', ], 'database' => [ 'driver' => 'database', 'connection' => env('DB_QUEUE_CONNECTION'), 'table' => env('DB_QUEUE_TABLE', 'jobs'), 'queue' => env('DB_QUEUE', 'default'), 'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90), 'after_commit' => false, ], 'beanstalkd' => [ 'driver' => 'beanstalkd', 'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'), 'queue' => env('BEANSTALKD_QUEUE', 'default'), 'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90), 'block_for' => 0, 'after_commit' => false, ], 'sqs' => [ 'driver' => 'sqs', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), 'queue' => env('SQS_QUEUE', 'default'), 'suffix' => env('SQS_SUFFIX'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 'after_commit' => false, ], 'redis' => [ 'driver' => 'redis', 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'), 'queue' => env('REDIS_QUEUE', 'default'), 'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90), 'block_for' => null, 'after_commit' => false, ], 'deferred' => [ 'driver' => 'deferred', ], 'background' => [ 'driver' => 'background', ], 'failover' => [ 'driver' => 'failover', 'connections' => [ 'database', 'deferred', ], ], ], /* |-------------------------------------------------------------------------- | Job Batching |-------------------------------------------------------------------------- | | The following options configure the database and table that store job | batching information. These options can be updated to any database | connection and table which has been defined by your application. | */ 'batching' => [ 'database' => env('DB_CONNECTION', 'sqlite'), 'table' => 'job_batches', ], /* |-------------------------------------------------------------------------- | Failed Queue Jobs |-------------------------------------------------------------------------- | | These options configure the behavior of failed queue job logging so you | can control how and where failed jobs are stored. Laravel ships with | support for storing failed jobs in a simple file or in a database. | | Supported drivers: "database-uuids", "dynamodb", "file", "null" | */ 'failed' => [ 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), 'database' => env('DB_CONNECTION', 'sqlite'), 'table' => 'failed_jobs', ], ]; ================================================ FILE: config/services.php ================================================ [ 'key' => env('POSTMARK_API_KEY'), ], 'resend' => [ 'key' => env('RESEND_API_KEY'), ], 'ses' => [ 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), ], 'slack' => [ 'notifications' => [ 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'), 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'), ], ], 'github' => [ 'token' => env('GITHUB_TOKEN'), ], ]; ================================================ FILE: config/session.php ================================================ env('SESSION_DRIVER', 'file'), /* |-------------------------------------------------------------------------- | Session Lifetime |-------------------------------------------------------------------------- | | Here you may specify the number of minutes that you wish the session | to be allowed to remain idle before it expires. If you want them | to expire immediately when the browser is closed then you may | indicate that via the expire_on_close configuration option. | */ 'lifetime' => (int) env('SESSION_LIFETIME', 120), 'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false), /* |-------------------------------------------------------------------------- | Session Encryption |-------------------------------------------------------------------------- | | This option allows you to easily specify that all of your session data | should be encrypted before it's stored. All encryption is performed | automatically by Laravel and you may use the session like normal. | */ 'encrypt' => env('SESSION_ENCRYPT', false), /* |-------------------------------------------------------------------------- | Session File Location |-------------------------------------------------------------------------- | | When utilizing the "file" session driver, the session files are placed | on disk. The default storage location is defined here; however, you | are free to provide another location where they should be stored. | */ 'files' => storage_path('framework/sessions'), /* |-------------------------------------------------------------------------- | Session Database Connection |-------------------------------------------------------------------------- | | When using the "database" or "redis" session drivers, you may specify a | connection that should be used to manage these sessions. This should | correspond to a connection in your database configuration options. | */ 'connection' => env('SESSION_CONNECTION'), /* |-------------------------------------------------------------------------- | Session Database Table |-------------------------------------------------------------------------- | | When using the "database" session driver, you may specify the table to | be used to store sessions. Of course, a sensible default is defined | for you; however, you're welcome to change this to another table. | */ 'table' => env('SESSION_TABLE', 'sessions'), /* |-------------------------------------------------------------------------- | Session Cache Store |-------------------------------------------------------------------------- | | When using one of the framework's cache driven session backends, you may | define the cache store which should be used to store the session data | between requests. This must match one of your defined cache stores. | | Affects: "dynamodb", "memcached", "redis" | */ 'store' => env('SESSION_STORE'), /* |-------------------------------------------------------------------------- | Session Sweeping Lottery |-------------------------------------------------------------------------- | | Some session drivers must manually sweep their storage location to get | rid of old sessions from storage. Here are the chances that it will | happen on a given request. By default, the odds are 2 out of 100. | */ 'lottery' => [2, 100], /* |-------------------------------------------------------------------------- | Session Cookie Name |-------------------------------------------------------------------------- | | Here you may change the name of the session cookie that is created by | the framework. Typically, you should not need to change this value | since doing so does not grant a meaningful security improvement. | */ 'cookie' => env( 'SESSION_COOKIE', Str::slug((string) env('APP_NAME', 'laravel')).'-session' ), /* |-------------------------------------------------------------------------- | Session Cookie Path |-------------------------------------------------------------------------- | | The session cookie path determines the path for which the cookie will | be regarded as available. Typically, this will be the root path of | your application, but you're free to change this when necessary. | */ 'path' => env('SESSION_PATH', '/'), /* |-------------------------------------------------------------------------- | Session Cookie Domain |-------------------------------------------------------------------------- | | This value determines the domain and subdomains the session cookie is | available to. By default, the cookie will be available to the root | domain without subdomains. Typically, this shouldn't be changed. | */ 'domain' => env('SESSION_DOMAIN'), /* |-------------------------------------------------------------------------- | HTTPS Only Cookies |-------------------------------------------------------------------------- | | By setting this option to true, session cookies will only be sent back | to the server if the browser has a HTTPS connection. This will keep | the cookie from being sent to you when it can't be done securely. | */ 'secure' => env('SESSION_SECURE_COOKIE'), /* |-------------------------------------------------------------------------- | HTTP Access Only |-------------------------------------------------------------------------- | | Setting this value to true will prevent JavaScript from accessing the | value of the cookie and the cookie will only be accessible through | the HTTP protocol. It's unlikely you should disable this option. | */ 'http_only' => env('SESSION_HTTP_ONLY', true), /* |-------------------------------------------------------------------------- | Same-Site Cookies |-------------------------------------------------------------------------- | | This option determines how your cookies behave when cross-site requests | take place, and can be used to mitigate CSRF attacks. By default, we | will set this value to "lax" to permit secure cross-site requests. | | See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value | | Supported: "lax", "strict", "none", null | */ 'same_site' => env('SESSION_SAME_SITE', 'lax'), /* |-------------------------------------------------------------------------- | Partitioned Cookies |-------------------------------------------------------------------------- | | Setting this value to true will tie the cookie to the top-level site for | a cross-site context. Partitioned cookies are accepted by the browser | when flagged "secure" and the Same-Site attribute is set to "none". | */ 'partitioned' => env('SESSION_PARTITIONED_COOKIE', false), /* |-------------------------------------------------------------------------- | Session Serialization |-------------------------------------------------------------------------- | | This value controls the serialization strategy for session data, which | is JSON by default. Setting this to "php" allows the storage of PHP | objects in the session but can make an application vulnerable to | "gadget chain" serialization attacks if the APP_KEY is leaked. | | Supported: "json", "php" | */ 'serialization' => 'json', ]; ================================================ FILE: config/statamic/antlers.php ================================================ env('STATAMIC_ANTLERS_DEBUGBAR', true), /* |-------------------------------------------------------------------------- | Guarded Variables |-------------------------------------------------------------------------- | | Any variable pattern that appears in this list will not be allowed | in any Antlers template, including any user-supplied values. | */ 'guardedVariables' => [ 'config.app.key', ], /* |-------------------------------------------------------------------------- | Guarded Tags |-------------------------------------------------------------------------- | | Any tag pattern that appears in this list will not be allowed | in any Antlers template, including any user-supplied values. | */ 'guardedTags' => [ ], /* |-------------------------------------------------------------------------- | Guarded Modifiers |-------------------------------------------------------------------------- | | Any modifier pattern that appears in this list will not be allowed | in any Antlers template, including any user-supplied values. | */ 'guardedModifiers' => [ ], ]; ================================================ FILE: config/statamic/api.php ================================================ env('STATAMIC_API_ENABLED', false), 'resources' => [ 'collections' => false, 'navs' => false, 'taxonomies' => false, 'assets' => false, 'globals' => false, 'forms' => false, 'users' => false, ], 'route' => env('STATAMIC_API_ROUTE', 'api'), /* |-------------------------------------------------------------------------- | Authentication |-------------------------------------------------------------------------- | | By default, the API will be publicly accessible. However, you may define | an API token here which will be used to authenticate requests. | */ 'auth_token' => env('STATAMIC_API_AUTH_TOKEN'), /* |-------------------------------------------------------------------------- | Middleware |-------------------------------------------------------------------------- | | Define the middleware / middleware group that will be applied to the | API route group. If you want to externally expose this API, here | you can configure a middleware-based authentication layer. | */ 'middleware' => env('STATAMIC_API_MIDDLEWARE', 'api'), /* |-------------------------------------------------------------------------- | Pagination |-------------------------------------------------------------------------- | | The numbers of items to show on each paginated page. | */ 'pagination_size' => 50, /* |-------------------------------------------------------------------------- | Caching |-------------------------------------------------------------------------- | | By default, Statamic will cache each endpoint until the specified | expiry, or until content is changed. See the documentation for | more details on how to customize your cache implementation. | | https://statamic.dev/content-api#caching | */ 'cache' => [ 'expiry' => 60, ], /* |-------------------------------------------------------------------------- | Exclude Keys |-------------------------------------------------------------------------- | | Here you may provide an array of keys to be excluded from API responses. | For example, you may want to hide things like edit_url, api_url, etc. | */ 'excluded_keys' => [ // ], ]; ================================================ FILE: config/statamic/assets.php ================================================ [ /* |-------------------------------------------------------------------------- | Route Prefix |-------------------------------------------------------------------------- | | The route prefix for serving HTTP based manipulated images through Glide. | If using the cached option, this should be the URL of the cached path. | */ 'route' => 'img', /* |-------------------------------------------------------------------------- | Require Glide security token |-------------------------------------------------------------------------- | | With this option enabled, you are protecting your website from mass image | resize attacks. You will need to generate tokens using the Glide tag | but may want to disable this while in development to tinker. | */ 'secure' => true, /* |-------------------------------------------------------------------------- | Image Manipulation Driver |-------------------------------------------------------------------------- | | The driver that will be used under the hood for image manipulation. | Supported: "gd", "imagick" or a class name of a custom driver. | */ 'driver' => 'gd', /* |-------------------------------------------------------------------------- | Save Cached Images |-------------------------------------------------------------------------- | | Enabling this will make Glide save publicly accessible images. It will | increase performance at the cost of the dynamic nature of HTTP based | image manipulation. You will need to invalidate images manually. | */ 'cache' => false, 'cache_path' => public_path('img'), /* |-------------------------------------------------------------------------- | Image Manipulation Defaults |-------------------------------------------------------------------------- | | You may define global defaults for all manipulation parameters, such as | quality, format, and sharpness. These can and will be overwritten | on the tag parameter level as well as the preset level. | */ 'defaults' => [ // 'quality' => 50, ], /* |-------------------------------------------------------------------------- | Image Manipulation Presets |-------------------------------------------------------------------------- | | Rather than specifying your manipulation params in your templates with | the glide tag, you may define them here and reference their handles. | They may also be automatically generated when you upload assets. | Containers can be configured to warm these caches on upload. | */ 'presets' => [ // 'small' => ['w' => 200, 'h' => 200, 'q' => 75, 'fit' => 'crop'], ], /* |-------------------------------------------------------------------------- | Generate Image Manipulation Presets on Upload |-------------------------------------------------------------------------- | | By default, presets will be automatically generated on upload, ensuring | the cached images are available when they are first used. You may opt | out of this behavior here and have the presets generated on demand. | */ 'generate_presets_on_upload' => true, ], /* |-------------------------------------------------------------------------- | Auto-Crop Assets |-------------------------------------------------------------------------- | | Enabling this will make Glide automatically crop assets at their focal | point (which is the center if no focal point is defined). Otherwise, | you will need to manually add any crop related parameters. | */ 'auto_crop' => true, /* |-------------------------------------------------------------------------- | Control Panel Thumbnail Restrictions |-------------------------------------------------------------------------- | | Thumbnails will not be generated for any assets any larger (in either | axis) than the values listed below. This helps prevent memory usage | issues out of the box. You may increase or decrease as necessary. | */ 'thumbnails' => [ 'max_width' => 10000, 'max_height' => 10000, ], /* |-------------------------------------------------------------------------- | Control Panel Video Thumbnails |-------------------------------------------------------------------------- | | When enabled, Statamic will generate thumbnails for videos. | Generated thumbnails are displayed in the Control Panel. | */ 'video_thumbnails' => true, /* |-------------------------------------------------------------------------- | File Previews with Google Docs |-------------------------------------------------------------------------- | | Filetypes that cannot be rendered with HTML5 can opt into the Google Docs | Viewer. Google will get temporary access to these files so keep that in | mind for any privacy implications: https://policies.google.com/privacy | */ 'google_docs_viewer' => false, /* |-------------------------------------------------------------------------- | Cache Metadata |-------------------------------------------------------------------------- | | Asset metadata (filesize, dimensions, custom data, etc) will get cached | to optimize performance, so that it will not need to be constantly | re-evaluated from disk. You may disable this option if you are | planning to continually modify the same asset repeatedly. | */ 'cache_meta' => true, /* |-------------------------------------------------------------------------- | Focal Point Editor |-------------------------------------------------------------------------- | | When editing images in the Control Panel, there is an option to choose | a focal point. When working with third-party image providers such as | Cloudinary it can be useful to disable Statamic's built-in editor. | */ 'focal_point_editor' => true, /* |-------------------------------------------------------------------------- | Enforce Lowercase Filenames |-------------------------------------------------------------------------- | | Control whether asset filenames will be converted to lowercase when | uploading and renaming. This can help you avoid file conflicts | when working in case-insensitive filesystem environments. | */ 'lowercase' => true, /* |-------------------------------------------------------------------------- | Additional Uploadable Extensions |-------------------------------------------------------------------------- | | Statamic will only allow uploads of certain approved file extensions. | If you need to allow more file extensions, you may add them here. | */ 'additional_uploadable_extensions' => [], /* |-------------------------------------------------------------------------- | Additional Filename Character Replacements |-------------------------------------------------------------------------- | | When uploading files, certain characters in filenames will be replaced | to ensure a safe filename. You may configure additional replacements. | These are in addition to the native ones. They are not overridable. | */ 'additional_filename_replacements' => [], /* |-------------------------------------------------------------------------- | SVG Sanitization |-------------------------------------------------------------------------- | | Statamic will automatically sanitize SVG files when uploaded to avoid | potential security issues. However, if you have a valid reason for | disabling this, and you trust your users, you may do so here. | */ 'svg_sanitization_on_upload' => true, /* |-------------------------------------------------------------------------- | FFmpeg |-------------------------------------------------------------------------- | | Statamic uses FFmpeg to extract thumbnails from videos to be shown in the | Control Panel. You may adjust the binary location and cache path here. | */ 'ffmpeg' => [ 'binary' => null, 'cache_path' => storage_path('statamic/glide/ffmpeg'), ], /* |-------------------------------------------------------------------------- | Replicator and Bard Set Preview Images |-------------------------------------------------------------------------- | | Replicator and Bard sets may have preview images to give users a visual | representation of the content within. Here you may specify the asset | container and folder where these preview images are to be stored. | */ 'set_preview_images' => [ 'container' => 'assets', 'folder' => 'set-previews', ], ]; ================================================ FILE: config/statamic/autosave.php ================================================ false, /* |-------------------------------------------------------------------------- | Default autosave interval |-------------------------------------------------------------------------- | | The default value may be set here and will apply to all collections. | However, it is also possible to manually adjust the value in the | each collection's config file. By default, this is set to 5s. | */ 'interval' => 5000, ]; ================================================ FILE: config/statamic/cp.php ================================================ env('CP_ENABLED', true), 'route' => env('CP_ROUTE', 'cp'), /* |-------------------------------------------------------------------------- | Authentication |-------------------------------------------------------------------------- | | Whether the Control Panel's authentication pages should be enabled, | and where users should be redirected in order to authenticate. | */ 'auth' => [ 'enabled' => true, 'redirect_to' => null, ], /* |-------------------------------------------------------------------------- | Start Page |-------------------------------------------------------------------------- | | When a user logs into the Control Panel, they will be taken here. | For example: "dashboard", "collections/pages", etc. | */ 'start_page' => 'dashboard', /* |-------------------------------------------------------------------------- | Dashboard Widgets |-------------------------------------------------------------------------- | | Here you may define any number of dashboard widgets. You're free to | use the same widget multiple times in different configurations. | */ 'widgets' => [ // ], /* |-------------------------------------------------------------------------- | Pagination |-------------------------------------------------------------------------- | | Here you may define the default pagination size as well as the options | the user can select on any paginated listing in the Control Panel. | */ 'pagination_size' => 50, 'pagination_size_options' => [10, 25, 50, 100, 500], /* |-------------------------------------------------------------------------- | Links to Documentation |-------------------------------------------------------------------------- | | Show contextual links to documentation throughout the Control Panel. | */ 'link_to_docs' => env('STATAMIC_LINK_TO_DOCS', true), /* |-------------------------------------------------------------------------- | Support Link |-------------------------------------------------------------------------- | | Set the location of the support link in the header. | */ 'support_url' => env('STATAMIC_SUPPORT_URL', 'https://statamic.com/support'), /* |-------------------------------------------------------------------------- | White Labeling |-------------------------------------------------------------------------- | | When in Pro Mode you may replace the Statamic name, logo, favicon, | and add your own CSS to the control panel to match your | company or client's brand. | */ 'custom_cms_name' => env('STATAMIC_CUSTOM_CMS_NAME', 'Statamic'), 'custom_logo_url' => env('STATAMIC_CUSTOM_LOGO_URL', null), 'custom_dark_logo_url' => env('STATAMIC_CUSTOM_DARK_LOGO_URL', null), 'custom_logo_text' => env('STATAMIC_CUSTOM_LOGO_TEXT', null), 'custom_favicon_url' => env('STATAMIC_CUSTOM_FAVICON_URL', null), 'custom_css_url' => env('STATAMIC_CUSTOM_CSS_URL', null), /* |-------------------------------------------------------------------------- | Thumbnails |-------------------------------------------------------------------------- | | Here you may define additional CP asset thumbnail presets. | */ 'thumbnail_presets' => [ // 'medium' => 800, ], ]; ================================================ FILE: config/statamic/editions.php ================================================ env('STATAMIC_PRO_ENABLED', false), 'addons' => [ // ], ]; ================================================ FILE: config/statamic/forms.php ================================================ resource_path('forms'), /* |-------------------------------------------------------------------------- | Email View Folder |-------------------------------------------------------------------------- | | The folder under resources/views where your email templates are found. | */ 'email_view_folder' => null, /* |-------------------------------------------------------------------------- | Send Email Job |-------------------------------------------------------------------------- | | The class name of the job that will be used to send an email. | */ 'send_email_job' => \Statamic\Forms\SendEmail::class, /* |-------------------------------------------------------------------------- | Exporters |-------------------------------------------------------------------------- | | Here you may define all the available form submission exporters. | You may customize the options within each exporter's array. | */ 'exporters' => [ 'csv' => [ 'class' => Statamic\Forms\Exporters\CsvExporter::class, ], 'json' => [ 'class' => Statamic\Forms\Exporters\JsonExporter::class, ], ], ]; ================================================ FILE: config/statamic/git.php ================================================ env('STATAMIC_GIT_ENABLED', false), /* |-------------------------------------------------------------------------- | Automatically Run |-------------------------------------------------------------------------- | | By default, commits are automatically queued when `Saved` or `Deleted` | events are fired. If you prefer users to manually trigger commits | using the `Git` utility interface, you may set this to `false`. | | https://statamic.dev/git-automation#committing-changes | */ 'automatic' => env('STATAMIC_GIT_AUTOMATIC', true), /* |-------------------------------------------------------------------------- | Queue Connection |-------------------------------------------------------------------------- | | You may choose which queue connection should be used when dispatching | commit jobs. Unless specified, the default connection will be used. | | https://statamic.dev/git-automation#queueing-commits | */ 'queue_connection' => env('STATAMIC_GIT_QUEUE_CONNECTION'), /* |-------------------------------------------------------------------------- | Dispatch Delay |-------------------------------------------------------------------------- | | When `Saved` and `Deleted` events queue up commits, you may wish to | set a delay time in minutes for each queued job. This can allow | for more consolidated commits when you have multiple users | making simultaneous content changes to your repository. | | Note: Not supported by default `sync` queue driver. | */ 'dispatch_delay' => env('STATAMIC_GIT_DISPATCH_DELAY', 0), /* |-------------------------------------------------------------------------- | Git User |-------------------------------------------------------------------------- | | The git user that will be used when committing changes. By default, it | will attempt to commit with the authenticated user's name and email | when possible, falling back to the below user when not available. | | https://statamic.dev/git-automation#git-user | */ 'use_authenticated' => true, 'user' => [ 'name' => env('STATAMIC_GIT_USER_NAME', 'Spock'), 'email' => env('STATAMIC_GIT_USER_EMAIL', 'spock@example.com'), ], /* |-------------------------------------------------------------------------- | Tracked Paths |-------------------------------------------------------------------------- | | Define the tracked paths to be considered when staging changes. Default | stache and file locations are already set up for you, but feel free | to modify these paths to suit your storage config. Referencing | absolute paths to external repos is also completely valid. | */ 'paths' => [ base_path('content'), base_path('users'), resource_path('addons'), resource_path('blueprints'), resource_path('fieldsets'), resource_path('forms'), resource_path('users'), resource_path('preferences.yaml'), resource_path('sites.yaml'), storage_path('forms'), public_path('assets'), ], /* |-------------------------------------------------------------------------- | Git Binary |-------------------------------------------------------------------------- | | By default, Statamic will try to use the "git" command, but you can set | an absolute path to the git binary if necessary for your environment. | */ 'binary' => env('STATAMIC_GIT_BINARY', 'git'), /* |-------------------------------------------------------------------------- | Commands |-------------------------------------------------------------------------- | | Define a list commands to be run when Statamic is ready to `git add` | and `git commit` your changes. These commands will be run once | per repo, attempting to consolidate commits where possible. | | https://statamic.dev/git-automation#customizing-commits | */ 'commands' => [ '{{ git }} add {{ paths }}', '{{ git }} -c "user.name={{ name }}" -c "user.email={{ email }}" commit -m "{{ message }}"', ], /* |-------------------------------------------------------------------------- | Push |-------------------------------------------------------------------------- | | Determine whether `git push` should be run after the commands above | have finished. This is disabled by default, but can be enabled | globally, or per environment using the provided variable. | | https://statamic.dev/git-automation#pushing-changes | */ 'push' => env('STATAMIC_GIT_PUSH', false), /* |-------------------------------------------------------------------------- | Ignored Events |-------------------------------------------------------------------------- | | Statamic will listen on all `Saved` and `Deleted` events, as well | as any events registered by installed addons. If you wish to | ignore any specific events, you may reference them here. | */ 'ignored_events' => [ // \Statamic\Events\UserSaved::class, // \Statamic\Events\UserDeleted::class, ], /* |-------------------------------------------------------------------------- | Locale |-------------------------------------------------------------------------- | | The locale to be used when translating commit messages, etc. By | default, the authenticated user's locale will be used, but | feel free to override this using the provided variable. | */ 'locale' => env('STATAMIC_GIT_LOCALE', null), ]; ================================================ FILE: config/statamic/graphql.php ================================================ env('STATAMIC_GRAPHQL_ENABLED', false), 'resources' => [ 'collections' => false, 'navs' => false, 'taxonomies' => false, 'assets' => false, 'globals' => false, 'forms' => false, 'sites' => false, 'users' => false, ], /* |-------------------------------------------------------------------------- | Queries |-------------------------------------------------------------------------- | | Here you may list queries to be added to the Statamic schema. | | https://statamic.dev/graphql#custom-queries | */ 'queries' => [ // ], /* |-------------------------------------------------------------------------- | Mutations |-------------------------------------------------------------------------- | | Here you may list mutations to be added to the Statamic schema. | | https://statamic.dev/graphql#custom-mutations */ 'mutations' => [ // ], /* |-------------------------------------------------------------------------- | Middleware |-------------------------------------------------------------------------- | | Here you may list middleware to be added to the Statamic schema. | | https://statamic.dev/graphql#custom-middleware | */ 'middleware' => [ // ], /* |-------------------------------------------------------------------------- | Caching |-------------------------------------------------------------------------- | | By default, Statamic will cache each request until the specified | expiry, or until content is changed. See the documentation for | more details on how to customize your cache implementation. | | https://statamic.dev/graphql#caching | */ 'cache' => [ 'expiry' => 60, ], /* |-------------------------------------------------------------------------- | Introspection |-------------------------------------------------------------------------- | | Introspection queries allow a user to see the schema and will power | development tools. This is "auto" by default, which will enable | it locally and keep it disabled everywhere else for security. | */ 'introspection' => env('STATAMIC_GRAPHQL_INTROSPECTION_ENABLED', 'auto'), ]; ================================================ FILE: config/statamic/live_preview.php ================================================ [ 'Laptop' => ['width' => 1440, 'height' => 900], 'Tablet' => ['width' => 1024, 'height' => 786], 'Mobile' => ['width' => 375, 'height' => 812], ], /* |-------------------------------------------------------------------------- | Additional Inputs |-------------------------------------------------------------------------- | | Additional fields may be added to the Live Preview header bar. You | may define a list of Vue components to be injected. Their values | will be added to the cascade on the front-end for you to use. | */ 'inputs' => [ // ], /* |-------------------------------------------------------------------------- | Force Reload Javascript Modules |-------------------------------------------------------------------------- | | To force a reload, Live Preview appends a timestamp to the URL on | script tags of type 'module'. You may disable this behavior here. | */ 'force_reload_js_modules' => true, /* |-------------------------------------------------------------------------- | Hot Reload Contents |-------------------------------------------------------------------------- | | Should the Live Preview embed be hot-reloaded when the content changes? | Only applies when "Refresh" is disabled on the live preview target. | */ 'hot_reload_contents' => true, ]; ================================================ FILE: config/statamic/markdown.php ================================================ [ 'default' => [ 'heading_permalink' => [ 'id_prefix' => '', 'fragment_prefix' => '', 'apply_id_to_heading' => true, 'html_class' => 'c-anchor', 'aria_hidden' => false, 'insert' => 'after', 'symbol' => '#', ], ], ], ]; ================================================ FILE: config/statamic/oauth.php ================================================ env('STATAMIC_OAUTH_ENABLED', false), 'email_login_enabled' => true, 'providers' => [ // 'github', ], 'routes' => [ 'login' => 'oauth/{provider}', 'callback' => 'oauth/{provider}/callback', ], /* |-------------------------------------------------------------------------- | Create User |-------------------------------------------------------------------------- | | Whether or not a user account should be created upon authentication | with an OAuth provider. If disabled, a user account will be need | to be explicitly created ahead of time. | */ 'create_user' => true, /* |-------------------------------------------------------------------------- | Merge User Data |-------------------------------------------------------------------------- | | When authenticating with an OAuth provider, the user data returned | such as their name will be merged with the existing user account. | */ 'merge_user_data' => true, /* |-------------------------------------------------------------------------- | Unauthorized Redirect |-------------------------------------------------------------------------- | | This controls where the user is taken after authenticating with | an OAuth provider but their account is unauthorized. This may | happen when the create_user option has been set to false. | */ 'unauthorized_redirect' => null, /* |-------------------------------------------------------------------------- | Remember Me |-------------------------------------------------------------------------- | | Whether or not the "remember me" functionality should be used when | authenticating using OAuth. When enabled, the user will remain | logged in indefinitely, or until they manually log out. | */ 'remember_me' => true, ]; ================================================ FILE: config/statamic/protect.php ================================================ null, /* |-------------------------------------------------------------------------- | Protection Schemes |-------------------------------------------------------------------------- | | Here you may define all of the protection schemes for your application | as well as their drivers. You may even define multiple schemes for | the same driver to easily protect different types of pages. | | Supported drivers: "ip_address", "auth", "password" | */ 'schemes' => [ 'ip_address' => [ 'driver' => 'ip_address', 'allowed' => ['127.0.0.1'], ], 'logged_in' => [ 'driver' => 'auth', 'login_url' => '/login', 'append_redirect' => true, ], 'password' => [ 'driver' => 'password', 'allowed' => ['secret'], 'field' => null, 'form_url' => null, ], ], ]; ================================================ FILE: config/statamic/revisions.php ================================================ env('STATAMIC_REVISIONS_ENABLED', false), ]; ================================================ FILE: config/statamic/routes.php ================================================ true, /* |-------------------------------------------------------------------------- | Enable Route Bindings |-------------------------------------------------------------------------- | | Whether route bindings for Statamic repositories (entry, taxonomy, | collections, etc) are enabled for front end routes. This may be | useful if you want to make your own custom routes with them. | */ 'bindings' => false, /* |-------------------------------------------------------------------------- | Action Route Prefix |-------------------------------------------------------------------------- | | Some extensions may provide routes that go through the frontend of your | website. These URLs begin with the following prefix. We've chosen an | unobtrusive default but you are free to select whatever you want. | */ 'action' => '!', /* |-------------------------------------------------------------------------- | Middleware |-------------------------------------------------------------------------- | | Define the middleware that will be applied to the web route group. | */ 'middleware' => 'web', ]; ================================================ FILE: config/statamic/search.php ================================================ env('STATAMIC_DEFAULT_SEARCH_INDEX', 'docs-'.config('docs.version')), /* |-------------------------------------------------------------------------- | Search Indexes |-------------------------------------------------------------------------- | | Here you can define all of the available search indexes. | */ 'indexes' => [ 'docs-'.config('docs.version') => [ 'driver' => env('SEARCH_DRIVER', 'meilisearch'), 'searchables' => ['docs:*', 'storybook:*'], 'fields' => [ 'title', 'search_title', 'content', 'origin_title', 'search_content', 'additional_context', 'hierarchy_lvl0', 'hierarchy_lvl1', 'url', ], 'settings' => [ 'rankingRules' => [ 'words', 'typo', 'proximity', 'attribute', 'exactness', 'origin_title:desc', 'hierarchy_lvl0:asc', ], 'searchableAttributes' => [ 'additional_context', 'hierarchy_lvl0', 'title', 'origin_title', 'search_title', 'search_content', 'hierarchy_lvl1', 'url', ], ], 'content_retriever' => App\Search\RequestContentRetriever::class, 'document_transformers' => [ App\Search\DocTransformer::class, ], ], ], /* |-------------------------------------------------------------------------- | Driver Defaults |-------------------------------------------------------------------------- | | Here you can specify default configuration to be applied to all indexes | that use the corresponding driver. For instance, if you have two | indexes that use the "local" driver, both of them can have the | same base configuration. You may override for each index. | */ 'drivers' => [ 'local' => [ 'path' => storage_path('statamic/search'), ], 'algolia' => [ 'credentials' => [ 'id' => env('ALGOLIA_APP_ID', ''), 'secret' => env('ALGOLIA_SECRET', ''), ], ], 'meilisearch' => [ 'credentials' => [ 'url' => env('MEILISEARCH_HOST', 'http://localhost:7700'), 'secret' => env('MEILISEARCH_KEY', ''), ], ], ], /* |-------------------------------------------------------------------------- | Search Defaults |-------------------------------------------------------------------------- | | Here you can specify default configuration to be applied to all indexes | regardless of the driver. You can override these per driver or per index. | */ 'defaults' => [ 'fields' => ['title'], ], /* |-------------------------------------------------------------------------- | Indexing Queue |-------------------------------------------------------------------------- | | Here you may configure the queue name and connection used when indexing | documents. | */ 'queue' => env('STATAMIC_SEARCH_QUEUE'), 'queue_connection' => env('STATAMIC_SEARCH_QUEUE_CONNECTION'), /* |-------------------------------------------------------------------------- | Chunk Size |-------------------------------------------------------------------------- | | Here you can configure the chunk size used when indexing documents. | The higher you make it, the more memory it will use, but the quicker | the indexing process will be. | */ 'chunk_size' => 100, ]; ================================================ FILE: config/statamic/stache.php ================================================ env('STATAMIC_STACHE_WATCHER', 'auto'), /* |-------------------------------------------------------------------------- | Cache Store |-------------------------------------------------------------------------- | | Here you may configure which Cache Store the Stache uses. | */ 'cache_store' => null, /* |-------------------------------------------------------------------------- | Stores |-------------------------------------------------------------------------- | | Here you may configure the stores that are used inside the Stache. | | https://statamic.dev/stache#stores | */ 'stores' => [ // ], /* |-------------------------------------------------------------------------- | Indexes |-------------------------------------------------------------------------- | | Here you may define any additional indexes that will be inherited | by each store in the Stache. You may also define indexes on a | per-store level by adding an "indexes" key to its config. | */ 'indexes' => [ // ], /* |-------------------------------------------------------------------------- | Locking |-------------------------------------------------------------------------- | | In order to prevent concurrent requests from updating the Stache at the | same time and wasting resources, it will be locked so that subsequent | requests will have to wait until the first one has been completed. | | https://statamic.dev/stache#locks | */ 'lock' => [ 'enabled' => true, 'timeout' => 30, ], /* |-------------------------------------------------------------------------- | Warming Optimization |-------------------------------------------------------------------------- | | These options control performance optimizations during Stache warming. | */ 'warming' => [ // Enable parallel store processing for faster warming on multi-core systems 'parallel_processing' => env('STATAMIC_STACHE_PARALLEL_WARMING', false), // Maximum number of parallel processes (0 = auto-detect CPU cores) 'max_processes' => env('STATAMIC_STACHE_MAX_PROCESSES', 0), // Minimum number of stores required to enable parallel processing 'min_stores_for_parallel' => env('STATAMIC_STACHE_MIN_STORES_PARALLEL', 3), // Concurrency driver: 'process', 'fork', or 'sync' 'concurrency_driver' => env('STATAMIC_STACHE_CONCURRENCY_DRIVER', 'process'), ], ]; ================================================ FILE: config/statamic/static_caching.php ================================================ env('STATAMIC_STATIC_CACHING_STRATEGY', null), /* |-------------------------------------------------------------------------- | Caching Strategies |-------------------------------------------------------------------------- | | Here you may define all of the static caching strategies for your | application as well as their drivers. | | Supported drivers: "application", "file" | */ 'strategies' => [ 'half' => [ 'driver' => 'application', 'expiry' => null, ], 'full' => [ 'driver' => 'file', 'path' => public_path('static'), 'lock_hold_length' => 0, 'permissions' => [ 'directory' => 0755, 'file' => 0644, ], ], ], /* |-------------------------------------------------------------------------- | Exclusions |-------------------------------------------------------------------------- | | Here you may define a list of URLs to be excluded from static | caching. You may want to exclude URLs containing dynamic | elements like contact forms, or shopping carts. | */ 'exclude' => [ 'class' => null, 'urls' => [ '/sitemap.xml', ], ], /* |-------------------------------------------------------------------------- | Invalidation Rules |-------------------------------------------------------------------------- | | Here you may define the rules that trigger when and how content would be | flushed from the static cache. See the documentation for more details. | If a custom class is not defined, the default invalidator is used. | | https://statamic.dev/static-caching | */ 'invalidation' => [ 'class' => null, 'rules' => [ // ], ], /* |-------------------------------------------------------------------------- | Ignoring Query Strings |-------------------------------------------------------------------------- | | Statamic will cache pages of the same URL but with different query | parameters separately. This is useful for pages with pagination. | If you'd like to ignore the query strings, you may do so. | */ 'ignore_query_strings' => false, 'allowed_query_strings' => [ // ], 'disallowed_query_strings' => [ // ], /* |-------------------------------------------------------------------------- | Nocache |-------------------------------------------------------------------------- | | Here you may define where the nocache data is stored. | | https://statamic.dev/tags/nocache#database | | Supported drivers: "cache", "database" | */ 'nocache' => 'cache', 'nocache_db_connection' => env('STATAMIC_NOCACHE_DB_CONNECTION'), /* |-------------------------------------------------------------------------- | Replacers |-------------------------------------------------------------------------- | | Here you may define replacers that dynamically replace content within | the response. Each replacer must implement the Replacer interface. | */ 'replacers' => [ \Statamic\StaticCaching\Replacers\CsrfTokenReplacer::class, \Statamic\StaticCaching\Replacers\NoCacheReplacer::class, ], /* |-------------------------------------------------------------------------- | Warm Queue |-------------------------------------------------------------------------- | | Here you may define the queue name and connection | that will be used when warming the static cache and | optionally set the "--insecure" flag by default. | */ 'warm_queue' => env('STATAMIC_STATIC_WARM_QUEUE'), 'warm_queue_connection' => env('STATAMIC_STATIC_WARM_QUEUE_CONNECTION'), 'warm_insecure' => env('STATAMIC_STATIC_WARM_INSECURE', false), /* |-------------------------------------------------------------------------- | Background Re-cache |-------------------------------------------------------------------------- | | When this is enabled, Statamic will re-cache URLs in the background, | overwriting the existing cache, without removing it first. | */ 'background_recache' => env('STATAMIC_BACKGROUND_RECACHE', false), 'recache_token' => env('STATAMIC_RECACHE_TOKEN'), 'recache_token_parameter' => '__recache', /* |-------------------------------------------------------------------------- | Shared Error Pages |-------------------------------------------------------------------------- | | You may choose to share the same statically generated error page across | all errors. For example, the first time a 404 is encountered it will | be generated and cached, and then served for all subsequent 404s. | | This is only supported for half measure. | */ 'share_errors' => false, ]; ================================================ FILE: config/statamic/system.php ================================================ env('STATAMIC_LICENSE_KEY'), /* |-------------------------------------------------------------------------- | Enable Multi-site |-------------------------------------------------------------------------- | | Whether Statamic's multi-site functionality should be enabled. It is | assumed Statamic Pro is also enabled. To get started, you can run | the `php please multisite` command to update your content file | structure, after which you can manage your sites in the CP. | | https://statamic.dev/multi-site | */ 'multisite' => false, /* |-------------------------------------------------------------------------- | Default Addons Paths |-------------------------------------------------------------------------- | | When generating addons via `php please make:addon`, this path will be | used by default. You can still specify custom repository paths in | your composer.json, but this is the path used by the generator. | */ 'addons_path' => base_path('addons'), /* |-------------------------------------------------------------------------- | Blueprints Path |-------------------------------------------------------------------------- | | Where your blueprint YAML files are stored. | */ 'blueprints_path' => resource_path('blueprints'), /* |-------------------------------------------------------------------------- | Fieldsets Path |-------------------------------------------------------------------------- | | Where your fieldset YAML files are stored. | */ 'fieldsets_path' => resource_path('fieldsets'), /* |-------------------------------------------------------------------------- | Send the Powered-By Header |-------------------------------------------------------------------------- | | Websites like builtwith.com use the X-Powered-By header to determine | what technologies are used on a particular site. By default, we'll | send this header, but you are absolutely allowed to disable it. | */ 'send_powered_by_header' => true, /* |-------------------------------------------------------------------------- | Date Format |-------------------------------------------------------------------------- | | This format will be used whenever a Carbon date is cast to a string on | front-end routes. It doesn't affect how dates are formatted in the CP. | You can customize this format using PHP's date string constants. | Setting this value to null will use Carbon's default format. | | https://www.php.net/manual/en/function.date.php | */ 'date_format' => 'F jS, Y', /* |-------------------------------------------------------------------------- | Timezone |-------------------------------------------------------------------------- | | Statamic will use this timezone when displaying dates on the front-end. | You can use any timezone supported by PHP. When set to null it will | fall back to the timezone defined in your `app.php` config file. | | https://www.php.net/manual/en/timezones.php | */ 'display_timezone' => null, /* |-------------------------------------------------------------------------- | Localize Dates in Modifiers |-------------------------------------------------------------------------- | | When using date-related modifiers, Carbon instances will be in UTC. | Enabling this setting will ensure that dates get localized into | the timezone defined in `display_timezone`. Otherwise you'll | need to manually localize dates in all of your templates. | */ 'localize_dates_in_modifiers' => true, /* |-------------------------------------------------------------------------- | Default Character Set |-------------------------------------------------------------------------- | | Statamic will use this character set when performing specific string | encoding and decoding operations; This does not apply everywhere. | */ 'charset' => 'UTF-8', /* |-------------------------------------------------------------------------- | Track Last Update |-------------------------------------------------------------------------- | | Statamic will automatically set an `updated_at` timestamp (along with | `updated_by`, where applicable) when specific content is updated. | In some situations, you may wish disable this functionality. | */ 'track_last_update' => false, /* |-------------------------------------------------------------------------- | Enable Cache Tags |-------------------------------------------------------------------------- | | Sometimes you'll want to be able to disable the {{ cache }} tags in | Antlers, so here is where you can do that. Otherwise, it will be | enabled all the time. | */ 'cache_tags_enabled' => env('STATAMIC_CACHE_TAGS_ENABLED', true), /* |-------------------------------------------------------------------------- | Intensive Operations |-------------------------------------------------------------------------- | | Sometimes Statamic requires extra resources to complete intensive | operations. Here you may configure system resource limits for | those rare times when we need to turn things up to eleven! | */ 'php_memory_limit' => '-1', 'php_max_execution_time' => '-1', 'ajax_timeout' => '600000', 'pcre_backtrack_limit' => '-1', /* |-------------------------------------------------------------------------- | Debugbar Integration |-------------------------------------------------------------------------- | | Statamic integrates with Laravel Debugbar to bring more detail to your | debugging experience. Here you may adjust various default options. | */ 'debugbar' => [ 'pretty_print_variables' => true, ], /* |-------------------------------------------------------------------------- | ASCII |-------------------------------------------------------------------------- | | During various string manipulations (e.g. slugification), Statamic will | need to make ASCII character conversions. Here you may define whether | or not extra characters get converted. e.g. "%" becomes "percent". | */ 'ascii_replace_extra_symbols' => false, /* |-------------------------------------------------------------------------- | Update References on Change |-------------------------------------------------------------------------- | | With this enabled, Statamic will attempt to update references to assets | and terms when moving, renaming, replacing, deleting, etc. This will | be queued, but it can disabled as needed for performance reasons. | */ 'update_references' => true, /* |-------------------------------------------------------------------------- | Always Augment to Query |-------------------------------------------------------------------------- | | By default, Statamic will augment relationship fields with max_items: 1 | to the result of a query, for example an Entry instance. Setting this | to true will augment to the query builder instead of the result. | */ 'always_augment_to_query' => false, /* |-------------------------------------------------------------------------- | Row ID handle |-------------------------------------------------------------------------- | | Rows in Grid, Replicator, and Bard fields will be given a unique ID using | the "id" field. You may need your own field named "id", in which case | you may customize the handle of the field that Statamic will use. | */ 'row_id_handle' => 'id', /* |-------------------------------------------------------------------------- | Fake SQL Queries |-------------------------------------------------------------------------- | | Enable while using the flat-file Stache driver to show fake "SQL" query | approximations in your database debugging tools — including Debugbar, | Laravel Telescope, and Ray with the ray()->showQueries() helper. | */ 'fake_sql_queries' => config('app.debug'), /* |-------------------------------------------------------------------------- | Layout |-------------------------------------------------------------------------- | | Define the default layout that will be used by views. | */ 'layout' => env('STATAMIC_LAYOUT', 'layout'), /* |-------------------------------------------------------------------------- | View Config Allowlist |-------------------------------------------------------------------------- | | Config keys that are allowed to be accessed in Antlers templates. Use | '@default' to include Statamic's default list. Add 'docs' so the docs | version switcher can access config.docs.version and config.docs.versions. | */ 'view_config_allowlist' => ['@default', 'docs'], ]; ================================================ FILE: config/statamic/templates.php ================================================ 'antlers', /* |-------------------------------------------------------------------------- | Code Style |-------------------------------------------------------------------------- | | Here you may configure the code generator's output style. | */ 'style' => [ 'line_ending' => 'auto', 'indent_type' => 'space', 'indent_size' => 4, 'final_newline' => false, ], /* |-------------------------------------------------------------------------- | Antlers Settings |-------------------------------------------------------------------------- | | Antlers specific template generation settings. | */ 'antlers' => [ 'use_components' => false, ], ]; ================================================ FILE: config/statamic/users.php ================================================ 'file', 'repositories' => [ 'file' => [ 'driver' => 'file', 'paths' => [ 'roles' => resource_path('users/roles.yaml'), 'groups' => resource_path('users/groups.yaml'), ], ], 'eloquent' => [ 'driver' => 'eloquent', ], ], /* |-------------------------------------------------------------------------- | Avatars |-------------------------------------------------------------------------- | | User avatars are initials by default, with custom options for services | like Gravatar.com. | | Supported: "initials", "gravatar", or a custom class name. | */ 'avatars' => 'initials', /* |-------------------------------------------------------------------------- | New User Roles |-------------------------------------------------------------------------- | | When registering new users through the user:register_form tag, these | roles will automatically be applied to your newly created users. | */ 'new_user_roles' => [ // ], /* |-------------------------------------------------------------------------- | New User Groups |-------------------------------------------------------------------------- | | When registering new users through the user:register_form tag, these | groups will automatically be applied to your newly created users. | */ 'new_user_groups' => [ // ], /* |-------------------------------------------------------------------------- | Registration form honeypot field |-------------------------------------------------------------------------- | | When registering new users through the user:register_form tag, | specify the field to act as a honeypot for bots | */ 'registration_form_honeypot_field' => null, /* |-------------------------------------------------------------------------- | User Wizard Invitation Email |-------------------------------------------------------------------------- | | When creating new users through the wizard in the control panel, | you may choose whether to be able to send an invitation email. | Setting to true will give the user the option. But setting | it to false will disable the invitation option entirely. | */ 'wizard_invitation' => true, /* |-------------------------------------------------------------------------- | Password Brokers |-------------------------------------------------------------------------- | | When resetting passwords, Statamic uses an appropriate password broker. | Here you may define which broker should be used for each situation. | You may want a longer expiry for user activations, for example. | */ 'passwords' => [ 'resets' => 'users', 'activations' => 'activations', ], /* |-------------------------------------------------------------------------- | Database |-------------------------------------------------------------------------- | | Here you may configure the database connection and its table names. | */ 'database' => config('database.default'), 'tables' => [ 'users' => 'users', 'role_user' => 'role_user', 'roles' => false, 'group_user' => 'group_user', 'groups' => false, 'webauthn' => 'webauthn', ], /* |-------------------------------------------------------------------------- | Authentication Guards |-------------------------------------------------------------------------- | | By default, Statamic will use the `web` authentication guard. However, | if you want to run Statamic alongside the default Laravel auth | guard, you can configure that for your cp and/or frontend. | */ 'guards' => [ 'cp' => 'web', 'web' => 'web', ], /* |-------------------------------------------------------------------------- | Impersonation |-------------------------------------------------------------------------- | | Here you can configure if impersonation is available, and what URL to | redirect to after impersonation begins. | */ 'impersonate' => [ 'enabled' => env('STATAMIC_IMPERSONATE_ENABLED', true), 'redirect' => env('STATAMIC_IMPERSONATE_REDIRECT', null), ], /* |-------------------------------------------------------------------------- | Elevated Sessions |-------------------------------------------------------------------------- | | Users may be required to reauthorize before performing certain | sensitive actions. This is called an elevated session. Here | you may configure the duration of the session in minutes. | */ 'elevated_session_duration' => 15, /* |-------------------------------------------------------------------------- | Enforce Two-Factor Authentication |-------------------------------------------------------------------------- | | Specify which user roles should be required to enable two-factor | authentication. Use "*" to enforce 2FA for all users, or "super_users" | to enforce it for super users. | */ 'two_factor_enforced_roles' => [], /* |-------------------------------------------------------------------------- | Default Sorting |-------------------------------------------------------------------------- | | Here you may configure the default sort behavior for user listings. | */ 'sort_field' => 'email', 'sort_direction' => 'asc', ]; ================================================ FILE: config/statamic/webauthn.php ================================================ true, /* |-------------------------------------------------------------------------- | Remember Me |-------------------------------------------------------------------------- | | Whether or not the "remember me" functionality should be used when | authenticating using WebAuthn. When enabled, the user will remain | logged in indefinitely, or until they manually log out. | */ 'remember_me' => true, /* |-------------------------------------------------------------------------- | Model |-------------------------------------------------------------------------- | | When using eloquent passkeys you can specify the model you want to use | */ 'model' => \Statamic\Auth\Eloquent\WebAuthnModel::class, ]; ================================================ FILE: config/torchlight.php ================================================ env('TORCHLIGHT_CACHE_DRIVER'), // Which theme you want to use. You can find all of the themes at // https://torchlight.dev/docs/themes. 'theme' => env('TORCHLIGHT_THEME', 'synthwave-84'), // Your API token from torchlight.dev. 'token' => env('TORCHLIGHT_TOKEN'), // If you want to register the blade directives, set this to true. 'blade_components' => false, // The Host of the API. 'host' => env('TORCHLIGHT_HOST', 'https://api.torchlight.dev'), // We replace tabs in your code blocks with spaces in HTML. Set // the number of spaces you'd like to use per tab. Set to // `false` to leave literal tabs in the HTML. 'tab_width' => 4, // If you pass a filename to the code component or in a markdown // block, Torchlight will look for code snippets in the // following directories. 'snippet_directories' => [ resource_path(), ], // Global options to control blocks-level settings. // https://torchlight.dev/docs/options 'options' => [ // Turn line numbers on or off globally. 'lineNumbers' => false, 'defaultLanguage' => 'antlers', // Control the `style` attribute applied to line numbers. // 'lineNumbersStyle' => '', 'fileStyle' => 'html', // Turn on +/- diff indicators. // 'diffIndicators' => true, // If there are any diff indicators for a line, put them // in place of the line number to save horizontal space. // 'diffIndicatorsInPlaceOfLineNumbers' => true, // When lines are collapsed, this is the text that will // be shown to indicate that they can be expanded. // 'summaryCollapsedIndicator' => '...', ], ]; ================================================ FILE: content/assets/.gitkeep ================================================ ================================================ FILE: content/assets/main.yaml ================================================ title: Main disk: assets ================================================ FILE: content/collections/.gitkeep ================================================ ================================================ FILE: content/collections/fieldtypes/array.md ================================================ --- title: Array meta_title: Array Fieldtype intro: Manage data in a `key:value` array format. overview: | The array fieldtype is used to manage `key: value` array data. It's similar to the [table](/fieldtypes/table) fieldtype but with a more strict data structure and compact user interface. screenshot: fieldtypes/screenshots/v6/array.webp screenshot_dark: fieldtypes/screenshots/v6/array-dark.webp options: - name: keys type: array description: > Define keys when using [keyed mode](#keyed-mode). Default: `null`. - name: mode type: string description: "Determine which [mode](#modes) to use. Default: `dynamic`." - name: value_header type: string description: > **Value** column heading displayed when using [dynamic mode](#dynamic-mode) Default: `Value`. - name: key_header type: string description: > **Key** column heading displayed when using [dynamic mode](#dynamic-mode) Default: `Key`. - name: add_button type: string description: > Add button text customization. Default: `Add Row`. - name: expand type: boolean description: > Save the field as an ordered list of `key` / `value` objects instead of a flat YAML mapping. Enable when you need numeric keys, stable option order with database-backed content stores, or to avoid YAML limitations with certain key types. Default: `false`. id: 457f17eb-c0ee-4345-bf90-88322abc212d --- ## Overview This fieldtype is used to manage `key: value` array data in the right situation. It's used for situations when there is data you would like to stay grouped together because there's only _one_ set and you don't want to use loops. If you'd like to have _lists_ of this type of data, you might want to use a [grid](/fieldtypes/grid) or [replicator](/fieldtypes/replicator) field. ## Modes The screenshot above depicts the three modes you can choose from. Two for when you know there is a fixed set of keys (keyed/single), and one for when you don't (dynamic). ### Keyed Mode The first field contains pre-defined keys. This will give the user a stricter input. They can only enter the values for the specified keys, and they cannot be reordered. ```yaml address: type: array keys: street: Street city: City country: Country ``` The keys can be specified with or without labels. The snippet above (and what's shown in the screenshot) uses labels. The following is an example of just keys. When using this syntax, the key and the label will be identical. ``` keys: - street - city - country ``` ### Single Mode Exactly the same restrictions and setup as keyed mode, except the user can only manage an array one item at a time, using a select box to switch between keys. ### Dynamic Mode The second field contains no pre-defined keys. This will allow the user to define them on the fly and re-arrange them. ```yaml address: type: array ``` Column headings can be set with `value_header` & `key_header`. ``` value_header: Type of Bacon key_header: Why is it awesome? ``` ### Expanded storage (`expand`) {#expanded-storage} By default, array data is stored as a YAML mapping (`key: value`). Set `expand: true` to store it as an ordered list of objects instead: ```yaml # expand: false (default) sizes: "42": Medium "7": Small # expand: true sizes: - key: "42" value: Medium - key: "7" value: Small ``` Use expanded storage when keys need to be **numbers** (YAML mappings treat numeric keys oddly), when you rely on **explicit row order** (for example with the Eloquent Driver and MySQL, where key order on associative arrays is not preserved), or when you edit raw YAML and want unambiguous structure. ```yaml inventory: type: array expand: true keys: sku: SKU qty: Quantity ``` [Augmentation](/augmentation) still exposes the field as a normal key/value structure for templates and Antlers, so `{{ inventory:sku }}` and nested variable syntax behave the same whether `expand` is on or off. ## Data Structure In the example above, the keyed mode and dynamic mode would save the exact same data (unless [expanded storage](#expanded-storage) is enabled—in that case the same logical keys are stored as a list of `key` / `value` objects). ```yaml address: street: 221B Baker Street city: London country: England ``` Single mode will only save data if it has been entered by the user. ```yaml address: England: '221 Baker Street, London' ``` ## Templating This fieldtype _is not_ [augmented](/augmentation). ::tabs You can use basic array access, nested variables, or the [foreach tag](/tags/foreach) to loop through all of the keys. All three of the following methods are equivalent. ```antlers // Array access {{ address }} {{ street }} {{ city }} {{ country }} {{ /address }} // Nested variables {{ address:street }} {{ address:city }} {{ address:country }} // Foreach tag: {{ foreach:address }} {{ value }} {{ /foreach:address }} ``` ::tab You can use basic array access or the `@foreach` directive to loop through all of the keys. ```blade // Nested variables {{ $address['street'] }} {{ $address['city'] }} {{ $address['country'] }} // Using foreach @foreach ($address as $key => $value) {{ $key }}: {{ $value }} @endforeach ``` :: ================================================ FILE: content/collections/fieldtypes/assets.md ================================================ --- title: Assets meta_title: Assets Fieldtype intro: Any time you want to list, display, or work with assets (external files with enhanced abilities), this is the way to do it. Upload, browse, reorder, delete, and even manage field data on individual assets. description: Upload files and use the Asset Browser to pick from existing files in your Asset Containers. screenshot: fieldtypes/screenshots/v6/assets-list.webp screenshot_dark: fieldtypes/screenshots/v6/assets-list-dark.webp options: - name: allow_uploads type: bool description: | Enable to allow uploading new files into the container. Default: `true`. - name: container type: string description: | The name of the desired [asset container](/assets#containers) to use for browsing, uploading, and managing assets. _Required when the site has more than one container._ - name: folder type: string description: > The folder (relative to the container) to begin browsing. Default: the root folder of the container. - name: dynamic type: string description: Assets will be placed in a subfolder based on the value of this field. See [dynamic folders](#dynamic-folders). You may use `id`, `slug`, or `author`. - name: min_files type: int description: > The minimum number of allowed files. Leave empty for no minimum. - name: max_files type: int description: > The maximum number of allowed files. Set to `null` for unlimited. If set to `1`, will be saved as a string instead of an array. Default: `null`. - name: mode type: string description: > Set to `list` to use the table layout mode, and `grid` to use the grid mode with larger thumbnails. Default: `grid`. - name: query_scopes type: string description: > Allows you to specify a [query scope](/extending/query-scopes-and-filters#scopes) which should be applied when retrieving selectable assets. You should specify the query scope's handle, which is usually the name of the class in snake case. For example: `MyAwesomeScope` would be `my_awesome_scope`. - name: restrict type: bool description: > If `true`, navigation within the asset browser will be disabled. Your users will be restricted to a specified container and folder. Default: `false`. id: d0c65546-74f1-4a15-89d5-1562a95ee2c6 --- ## Overview The assets fieldtype is used to manage and relate files with your entries. From the fieldtype you can manage custom fields on the assets themselves (learn more about that in the [assets guide](/assets)), preview full size images or rich media files, and even set focal points for cropping. Files are rearrangeable via drag-and-drop. ## UI Modes The list mode is shown in the previous screenshot, while the grid mode is shown below. There are no functional differences, only visual ones. List mode is more compact – useful if you're not primarily managing images.
Assets List mode Assets List mode
List mode reveals a fanny pack in all its glory. And if you’re British—go on, have a little chuckle.
## Data Structure Data is stored as an array of image paths _relative to the asset container_. Each asset's full URL is generated dynamically on the frontend based on the image path and its container. This allows containers to be a bit more portable by avoiding fully hardcoded file paths. If `max_files` is set to `1`, a string will be saved instead of an array. ``` yaml # Default YAML gallery_images: - fresh-prince.jpg - dj-jazzy-jeff.jpg - uncle-phil.jpg # With max_files: 1 hero_image: surf-boards.jpg ``` ## Templating The Asset fieldtype uses [augmentation](/augmentation) to automatically relate the files with their Asset records, pull in custom and meta data, and resolve all image paths based on the container. ::tabs By using a tag pair syntax, you'll be able to output variables for each asset: ```antlers {{ gallery_images }} {{ alt }} {{ /gallery_images }} {{ hero_image }} {{ alt }} {{ /hero_image }} ``` ::tab You can use the `@foreach` directive to loop over each asset and output its variables: ```blade @foreach ($gallery_images as $asset) {{ $asset->alt }} @endforeach {{-- Assuming $hero_image is max_files: 1 --}} {{ $hero_image->alt }} ``` :: ```html Will Smith as the Fresh Prince Jeffrey Allen Townes as DJ Jazzy Jeff James Avery as Uncle Phil 3 colorful surf boards ``` The same tag pair syntax can be used regardless of your `max_files` setting. ::tabs If you have `max_files: 1`, you can also use a single tag syntax to directly use a variable inside the asset. Without a second tag part, the URL will be used. ```antlers {{ hero_image }} {{ hero_image:url }} {{ hero_image:alt }} ``` ::tab If you have `max_files: 1`, you can use property access to output variables within the asset. When output as a string, the URL will be used. ```blade {{ $hero_image }} {{ $hero_image->url }} {{ $hero_image->alt }} ``` :: ```html /assets/surf-boards.jpg /assets/surf-boards.jpg 3 colorful surf boards ``` ### Variables Inside an asset variable's tag pair you'll have access to the following variables. | Variable | Description | |----------|-------------| | `url` | Web-friendly URL to the file. | | `path` | Path to the file relative to asset container | | `permalink` | Absolute URL to the file including your site URL. | | `basename` | Name of the file with file extension | | `blueprint` | Which blueprint is managing the asset container | | `container` | Which asset container the file is in | | `edit_url` | URL to edit asset in the Control Panel | | `extension` | File extension | | `filename` | Name of the file without file extension | | `folder` | Which folder the file is in | | `is_audio` | `true` when file is one of `aac`, `flac`, `m4a`, `mp3`, `ogg`, or `wav`. | | `is_image` | `true` when file is one of `jpg`, `jpeg`, `png`, `gif`, or `webp`. | | `is_svg` | `true` when file is an `svg`. | | `is_video` | `true` when file is one of `h264`, `mp4`, `m4v`, `ogv`, or `webm`. | | `last_modified` | Formatted date string of the last modified time | | `last_modified_instance` | [Carbon][carbon] instance of the last modified time | | `last_modified_timestamp` | Unix timestamp of the last modified time | | `size` | Pre-formatted file size (`3.48 MB`) | | `size_b` | File size in bytes | | `size_kb` | File size in kilobytes | | `size_mb` | File size in megabytes | | `size_gb` | File size in gigabytes | ### Image Assets | Variable | Description | |----------|-------------| | `height` | height of an image, in pixels | | `width` | width of an image, in pixels | | `focus_css` | CSS `background-image` property for a focal point | `orientation` | Is one of `portrait`, `landscape`, `square`, or `null`. | | `ratio` | An image's ratio (`1.77`) | :::tip You can use [Glide](/tags/glide) to crop, flip, sharpen, pixelate, and perform other sweet image manipulations. ::: ### Asset Field Data ::tabs All custom data set on the assets will also be available inside the asset tag loop. ```antlers {{ gallery_images }}
{{ alt }}
{{ caption }}
{{ /gallery_images }} ``` ::tab All custom data set on the assets will also be available on the asset instance. ```blade @foreach ($gallery_images as $image) {{ $image->alt }} @if ($caption = $image->caption)
{{ $caption }}
@endif @endforeach ``` :: ## Dynamic Folders In addition to selecting which container and folder assets will be uploaded into, you may configure the field to have a dedicated subfolder based on an entry's value. For example, you may want to place all the images for a blog post in its own directory. ```yaml type: assets container: images folder: blog dynamic: slug ``` This would result in `image.jpg` being uploaded to `path/to/images/blog/my-entry-slug/image.jpg`. You may target the entry's `id`, `slug`, or `author` values. :::warning The values are *not* kept in sync. For example, if you change the entry's slug, the asset folder will not be renamed. You may rename the folder manually via a control on the field, or within the asset browser. ::: [carbon]: https://carbon.nesbot.com/docs/ ================================================ FILE: content/collections/fieldtypes/bard.md ================================================ --- title: Bard description: "Rich article writing and block-based layouts made easy." intro: | Bard is more than just a content editor, and more flexible than a block-based editor. **It is designed to provide a delightful and powerful writing experience** with unparalleled flexibility on your front-end. screenshot: fieldtypes/screenshots/v6/bard-with-sets.webp screenshot_dark: fieldtypes/screenshots/v6/bard-with-sets-dark.webp options: - name: allow_source type: boolean description: | Controls whether the "show source code" button is available to your editors. Default: `true`. - name: sets type: array description: An array containing sets of fields. If you don't provide any sets, Bard will act like a basic text/WYSIWYG editor and you won't see the "Add Set" button. - name: buttons type: array description: | An array of buttons you want available in the toolbar. You can choose from `h1`, `h2`, `h3`, `h4`, `h5`, `h6`, `bold`, `italic`, `small`, `underline`, `strikethrough`, `unorderedlist`, `orderedlist`, `removeformat`, `quote`, `anchor`, `image`, `table`, `code` (inline), `codeblock`, and `horizontalrule`. These are the defaults: ![Bard Buttons](/img/fieldtypes/screenshots/bard-buttons.png) {.mt-4} You can override the default buttons using the `Bard::setDefaultButtons()` method: ```php \Statamic\Fieldtypes\Bard::setDefaultButtons(['h2', 'h3', 'bold', 'italic']); ``` When you have the `image` button toggled, make sure to define an Asset Container in the Bard field's settings, otherwise the button won't show. - name: target_blank type: boolean description: | Automatically add `target="_blank"` on links by default. You'll be able to override this per-link. Default: `false`. - name: link_noopener type: boolean description: | Set `rel="noopener"` on all created links. Default: `false`. - name: link_noreferrer type: boolean description: | Set `rel="noreferrer"` on all created links. Default: `false`. - name: fullscreen type: boolean description: | Enable the option to toggle into fullscreen mode. Default: `true`. - name: collapse type: boolean description: > Expand (`true`) or collapse (`false`) all sets by default. Default: `false`. - name: container type: string description: > An asset container ID. When specified, the fieldtype will allow the user to add a link to an asset from the specified container. - name: inline type: boolean description: | Switch the field to inline mode. Block elements such as sets, headings and images are not supported in inline mode and should not be enabled. - name: inline_hard_breaks type: boolean description: | Enable support for hard breaks in inline mode. Only works when `inline` is set to `true`. Default: `false`. - name: toolbar_mode type: string description: > Choose which style of toolbar you prefer, `fixed` or `floating`. Default: `fixed`. - name: reading_time type: boolean description: > Show estimated reading time at the bottom of the field. Default: `false`. - name: word_count type: boolean description: > Show the word count at the bottom of the field. Default: `false`. - name: save_html type: boolean description: > Save as HTML instead of structured data. This simplifies templates so you don't need to loop through the structured nodes. Only works while no sets are defined. Default: `false`. - name: always_show_set_button type: boolean description: > Always show the "Add Set" button. Default: `false`. - name: remove_empty_nodes type: string description: > Choose how to deal with empty nodes. Options: `false`, `true`, `trim`. Default: `false`. id: f4bf58d3-cbce-4957-b883-d92fd4791e89 --- ## Overview Bard is our recommended fieldtype for creating long form content from the control panel. It's highly configurable, intuitive, user-friendly, and writes impeccable HTML (thanks to [ProseMirror][prosemirror]). Bard also has the ability to manage "sets" of fields inline with your text. These sets can contain any number of other fields of any [fieldtype](/fieldtypes), and can be collapsed and neatly rearranged in your content. ## Working With Sets {#sets} You can use any fieldtypes inside your Bard sets. Make sure to compare the experience with the other meta-fields: [Grid](/fieldtypes/grid) and [Replicator](/fieldtypes/replicator). You can even use Grids and Replicators inside your Bard sets. Just remember that because you can doesn't mean you should. Your UI experience can vary greatly. ### Set Previews New to Statamic v6, you can add an image preview of your set, _as well as_ an icon. Previews make it easy to identify sets by showing a screenshot of what the rendered set might look like on the front-end. Clients can now say “ah, that one” without pretending to know the names you carefully gave them. #### Configuring Set Previews To add a set preview, click the little "pencil" icon next to the set name.
Bard Set Edit Preview Bard Set Edit Preview
Let's give the set a preview.
Once you're in the set editor, you can add a preview image and icon. Here we're showing a lovely screenshot of what the newsletter signup form might look like on the front-end. We can even add some instructions to explain how the set is used.
Bard Set Previews Bard Set Previews
Behold a Preview Image, for the love of all clients.
#### Set Previews in Action Once you've set a preview image, users adding a bard set can hover over the set to preview what it might look like on the frontend. You can view previews in two different UI modes: in a list of set names, or in a grid of sets with their preview images.
Bard Set Previews in the CP Bard Set Previews in the CP
A preview in a list of sets.
Bard Set Previews in the CP Bard Set Previews in the CP
A preview in a grid of sets. Previews fall back to the set icon if no preview image is set.
### Custom Set Icons You can change the icons available in the set picker by configuring an icon set in a service provider. For example, you can drop this into your `AppServiceProvider`'s `boot` method: ```php use Statamic\Fieldtypes\Sets; public function boot() { Sets::useIcons('heroicons', resource_path('svg/heroicons')); } ``` ## Data Structure Bard stores your data as a [ProseMirror document](https://prosemirror.net/docs/ref/#model.Document_Structure). You should never need to interact with this data directly, thanks to [augmentation](/augmentation). ## Templating ### Without Sets If you are using Bard just as a rich text editor and have no need for sets you would use a single tag to render the content. ::tabs ```antlers {{ bard_field }} ``` ::tab ```blade {!! $bard_field !!} ``` :: ### With Sets When working with sets, you should use the tag pair syntax and `if/else` conditions on the `type` variable to style each set accordingly. **Note**: any content that is entered _not_ in a set (i.e. your normal rich-text content) needs to be rendered using the "text" type. See the first condition using "text." ::tabs ```antlers {{ bard_field }} {{ if type == "text" }}
{{ text }}
{{ elseif type == "poll" }}
{{ question }}
    {{ options }}
  • {{ text }}
  • {{ /options }}
{{ elseif type == "hero_image" }}
{{ caption }}
{{ /if }} {{ /bard_field }} ``` ::tab ```blade @foreach ($bard_field_with_sets as $set) @if ($set->type === 'text')
{!! $set->text !!}
@elseif ($set->type === 'poll')
{{ $set->question }}
    @foreach ($set->options as $option)
  • {{ $option->text }}
  • @endforeach
@elseif ($set->type === 'hero_image' && $hero_image = $set->hero_image)
{{ $hero_image->caption }}
@endif @endforeach ``` :: An alternative approach (for those who like DRY or small templates) is to create multiple "set" partials and pass the data to them dynamically, moving the markup into corresponding partials bearing the set's name. ::tabs ::tab antlers ```antlers {{ bard_field }} {{ partial src="sets/{type}" }} {{ /bard_field }} ``` ``` files theme:serendipity-light resources/views/partials/sets/ gallery.antlers.html hero_image.antlers.html poll.antlers.html text.antlers.html video.antlers.html ``` ::tab blade :::tip By using `[...$set]`, you can access the set variables within the set's Blade file without having to reference `$set` for each variable. For example, `{!! $set->text !!}` becomes `{!! $text !!}`. ::: ```blade @foreach ($bard_field_with_sets as $set) @include('fieldtypes.bard.sets.'.$set->type, [...$set]) @endforeach ``` ``` files theme:serendipity-light resources/views/partials/sets/ gallery.blade.php hero_image.blade.php poll.blade.php text.blade.php video.blade.php ``` :: ## Extending Bard Bard uses [TipTap](https://tiptap.dev/) (which in turn is built on top of [ProseMirror][prosemirror]) as the foundation for our quintessential block-based editor. [prosemirror]: https://prosemirror.net/ ### Required Reading Before you attempt to create any Bard extensions, it is wise to learn how to write a Tiptap extension first. Otherwise you'd be trying to learn how to ride a motorcycle before you can even ride a bike. Or a unicycle before you can juggle. To have a better understanding of how to write a Tiptap extension, you'd in turn benefit greatly on reading about how ProseMirror works. :::tip Writing custom extensions for Bard is pretty complicated, but can be rewarding and provide powerful results. ::: In short, here's a quick-start of the things you should probably start with: - [The ProseMirror guide](https://prosemirror.net/docs/guide/) — Yes, it's really long, but you should at least pretend to read it - Checking out the [The Tiptap documentation](https://tiptap.dev/docs/editor/getting-started/overview) and [code samples for the core Tiptap extensions](https://github.com/ueberdosis/tiptap/tree/develop/packages), so you can understand how Tiptap relates to ProseMirror - If you don't know [how to extend the control panel](/control-panel/css-javascript) yet, go ahead and read up on that first. The code snippets later will be part of your extension to the control panel. Alternatively, you may also [extend the control panel through the creation of an addon](/addons/building-an-addon). - Come back here again and keep on going. ### Adding New Extensions You may add your own Tiptap extensions to Bard using the `addExtension` method. The callback may return a single extension, or an array of them. ``` js const { Node, Mark, Extension } = Statamic.$bard.tiptap.core; Statamic.$bard.addExtension(() => Node.create({...})); ``` ``` js Statamic.$bard.addExtension(() => { return [ Node.create({...}), Mark.create({...}), Extension.create({...}), ] }); ``` Check out [Tiptap's custom extension documentation](https://tiptap.dev/docs/editor/extensions/custom-extensions) and [code samples for the core Tiptap extensions](https://github.com/ueberdosis/tiptap/tree/develop/packages) to find out how to write an extension. If you're providing a new mark or node and intend to use this Bard field on the front-end, you will also need to create a Mark or Node class to be used by the PHP [renderer](#tiptap-php-rendering). :::tip If you need any other Tiptap helpers or utilities you can use our [Tiptap API](#tiptap-api). ::: ### Replacing Existing Extensions If you'd like to replace a [native extension](https://github.com/ueberdosis/tiptap/tree/develop/packages) (e.g. headings or paragraphs) you can use the `replaceExtension` method. It takes the `name` of the extension, and a callback that returns a single extension instance. ```js const { Node } = Statamic.$bard.tiptap.core; Statamic.$bard.replaceExtension('heading', ({ extension, bard }) => { return Node.create({ name: 'heading', ... }); }); ``` The callback will provide you with the existing extension instance, so if you are doing simple tweaks to an extension (e.g. customizing an input rule) you can simply extend the existing instance. Then you don't need to author an entire extension: ```js const { nodeInputRule } = Statamic.$bard.tiptap.core; Statamic.$bard.replaceExtension('heading', ({ extension, bard }) => { return extension.extend({ addInputRules() { return [ nodeInputRule({...}), ]; }, }); }); ``` You can also reconfigure extensions (e.g. to add Tailwind classes to headings or disable specific "smart typography" rules): ```js Statamic.$bard.replaceExtension('heading', ({ extension, bard }) => { return extension.configure({ HTMLAttributes: { class: 'font-bold', }, }); }); ``` ```js Statamic.$bard.replaceExtension('typography', ({ extension, bard }) => { return extension.configure({ oneHalf: false, oneQuarter: false, threeQuarters: false, }); }); ``` ### Buttons To add a button to the toolbar, provide a callback to the `buttons` method. The callback will receive two arguments: - `buttons` - an array of the existing buttons in the toolbar (more about that in a moment) - `button` - a function that wraps your button objects The callback may return a `button` object, or an array of them. ``` js Statamic.$bard.buttons((buttons, button) => { return button({ name: 'custom_bold', text: __('Custom Bold'), // Tooltip text svg: 'bold', // Name of an SVG icon html: '...', // Custom icon HTML args: { class: 'font-bold' }, // The command arguments command: (editor, args) => editor.chain().focus().setCustomBold(args).run(), // The command to run activeName: 'customBold', // The active node/mark type that will activate this button (falls back to name) active: (editor, args) => editor.isActive('bold'), // Active check callback (overrides activeName) visibleWhenActive: 'example', // The active node/mark type that will show this button (always visible if not set) visible: (editor, args) => editor.isActive('example'), // Visible check callback (overrides visibleWhenActive) }); }); ``` ``` js Statamic.$bard.buttons((buttons, button) => [ button({...}), button({...}), ]); ``` Returning values to the `buttons` method will push them onto the end. If you need more control, you can manipulate the supplied `buttons` argument, and then return nothing. For example, we'll add a button after wherever the existing bold button happens to be: ``` js Statamic.$bard.buttons((buttons, button) => { const indexOfBold = _.findIndex(buttons, { name: 'bold' }); buttons.splice(indexOfBold + 1, 0, button({...})); }); ``` :::tip Using the `button()` method will make the button only appear if the Bard field has been configured to show your button. If you'd like your button to appear on all Bard fields, regardless of whether it's been configured to use that button, you can just return an object. Don't wrap with `button()`. ::: ### Tiptap API In your extensions, you may need to use functions from the `tiptap` library. Rather than importing the library yourself and bloating your JS files, you may use methods through our API. ``` js Statamic.$bard.tiptap.core; // `tiptap` (core, commands, utilities and helpers) Statamic.$bard.tiptap.pm.state; // `prosemirror-state` Statamic.$bard.tiptap.pm.model; // `prosemirror-model` Statamic.$bard.tiptap.pm.view; // `prosemirror-view` ``` You could shorten things up by using destructuring. For example: ``` js const { InputRule, insertText, getAttributes } = Statamic.$bard.tiptap.core; new InputRule(...); insertText(...); getAttributes(...); ``` ### Tiptap PHP Rendering If you have created an extension on the JS side to be used inside the Bard fieldtype, you will need to be able to render it on the PHP side (in your views). The Bard `Augmentor` class is responsible for converting the ProseMirror structure to HTML. You can use the `addExtension` or `replaceExtension` methods to bind an extension class into the renderer. Your AppServiceProvider's `boot` method is a good place to do this. ``` php use Statamic\Fieldtypes\Bard\Augmentor; public function boot() { // Pass an object Augmentor::addExtension('myExtension', new MyExtension); // or a closure. You will be passed the bard fieldtype and an array of options as arguments. Augmentor::addExtension('myExtension', function ($bard, $options) { return new MyExtension(['foo' => $bard->config('should_foo')]; }); // Same for replacing extensions. Augmentor::replaceExtension('paragraph', new MyCustomParagraph); // Closures too. There will be an additional argument at the front which is the existing extension. Augmentor::replaceExtension('paragraph', function ($existing, $bard, $options) { return new CustomParagraph; }); } ``` Check out [code samples for the core Tiptap extensions](https://github.com/ueberdosis/tiptap-php/tree/main/src) to find out how to write PHP extensions. ================================================ FILE: content/collections/fieldtypes/button_group.md ================================================ --- title: 'Button Group' description: 'Buttons you click. You can only choose one.' intro: | Buttons. Create some options and let your users select one and only one. May they choose wisely. screenshot: fieldtypes/screenshots/v6/button-group.webp screenshot_dark: fieldtypes/screenshots/v6/button-group-dark.webp options: - name: clearable type: boolean description: | Allow deselecting all options, making `null` a possible value. Default: `false`. - name: options type: array description: 'Sets of key/value pairs define the values and labels of the buttons.' id: 26751221-fdc8-47c6-97f0-bf4997319482 --- ## Overview The button group fieldtype is a multiple choice input where you only get one choice. It saves the chosen option from a preset list. ## Configuring Use the `options` setting to define a list of values and labels. ``` yaml seat_choice: type: button_group instructions: Choose your airline seat. Choose wisely. options: left: Left middle: Middle right: Right ``` You may omit the labels and just specify keys. If you use this syntax, the value and label will be identical. ``` yaml options: - Left - Middle - Right ``` ### Options in blueprint YAML Like the [select](/fieldtypes/select) fieldtype, `options` in the blueprint are saved in **expanded** form (ordered `key` / `value` rows) so option order is preserved in all storage backends. Hand-written blueprints should follow that structure if you are not using the visual editor. ```yaml options: - key: left value: Left - key: middle value: Middle ``` ## Data Structure The chosen option is stored as a string. If you only specified values for the `options` array, then the label will be saved. ``` yaml seat_choice: middle ``` ## Templating It's a string, so you can just use that value. ::tabs ::tab antlers ```

I love sitting in the {{ seat_choice }} seat. A lot.

``` ::tab blade ```blade

I love sitting in the {{ $seat_choice }} seat. A lot.

``` :: ```html

I love sitting in the middle seat. A lot.

``` ================================================ FILE: content/collections/fieldtypes/checkboxes.md ================================================ --- title: Checkboxes description: Boxes you check. You can check 'em all. intro: > Checkboxes! Make some checkboxes, click the checkboxes, and store a record of which boxes of which ones you clicked. They're boxes you check. screenshot: fieldtypes/screenshots/v6/checkboxes.webp screenshot_dark: fieldtypes/screenshots/v6/checkboxes-dark.webp options: - name: inline type: bool description: > Show the checkboxes next to each other in a row instead of stacked vertically. Default: `false` - name: options type: array description: > Sets of key/value pairs define the values and labels of the checkbox options. id: f922cb9b-6fc9-4249-adf4-59aa46285c13 --- ## Overview The checkboxes fieldtype is a multiple choice input. It saves one or more options chosen from a preset list. In other words, they're boxes you check. ## Configuring Use the `options` setting to define a list of values and labels. ``` yaml favorites: type: checkboxes instructions: Choose up to 3 favorite foods. options: donuts: Donuts icecream: Ice Cream brownies: Brownies ``` You may omit the labels and just specify keys. If you use this syntax, the value and label will be identical. ``` yaml options: - Donuts - Ice Cream - Brownies ``` ### Options in blueprint YAML See [Select · Options in blueprint YAML](/fieldtypes/select#options-in-blueprint-yaml)—checkbox options in the blueprint use the same expanded `key` / `value` rows so order is preserved everywhere. ## Data Structure The values are stored as a YAML array. If you only specified values for the `options` array, then the labels will be saved. ``` yaml favorites: - donuts - icecream ``` ## Templating You can loop through the checked items and access the value and label of each item inside the loop. ::tabs ::tab antlers ```
    {{ favorites }}
  • {{ value }}
  • {{ /favorites }}
``` ::tab blade ```blade
    @foreach($favorites as $favorite) {{-- You can also access $favorite['key'] and $favorite['label'] --}}
  • {{ $favorite['value'] }}
  • @endforeach
``` :: ```html
  • donuts
  • icecream
``` ::tabs ::tab antlers To conditionally check if a value has been selected, you can combine the [pluck](/modifiers/pluck) and [contains](/modifiers/contains) modifiers: ```antlers {{ if favorites | pluck('value') | contains('donuts') }} Contains donuts! {{ /if }} ``` ::tab blade To conditionally check if a value has been selected, you can combine the pluck and contains collection methods: ```blade @if (collect($favorites)->pluck('value')->contains('donuts')) Contains donuts! @endif ``` :: ### Variables Inside an asset variable's tag pair you'll have access to the following variables. | Variable | Description | |----------|-------------| | `key` | The zero-index count of the current item | | `value` | The stored value of the checkbox | | `label` | The label of the checkbox item from the field config | ================================================ FILE: content/collections/fieldtypes/code.md ================================================ --- title: Code description: 'Write code and see it highlight. But will you choose spaces or tabs?' intro: What are you doing writing code in a browser?! Just kidding, it's fine. We made it easy, flexible, and pretty too. We use this fieldtype a lot. screenshot: fieldtypes/screenshots/v6/code.webp screenshot_dark: fieldtypes/screenshots/v6/code-dark.webp options: - name: theme type: string description: | Choose between `light` and `material` (dark) themes. Default: `light`. - name: mode type: string description: | Set a default language for syntax highlighting. Your choices include: - `clike` - `css` - `diff` - `go` - `haml` - `handlebars` - `htmlmixed` - `less` - `markdown` - `gfm` - `nginx` - `text/x-java` - `javascript` - `jsx` - `text/x-objectivec` - `php` - `python` - `ruby` - `scss` - `shell` - `sql` - `twig` - `vue` - `xml` - `yaml-frontmatter` - name: mode_selectable type: boolean description: | Whether the `mode` can be selected by the user in the publish form. Enabling this will change the GraphQL type from a string to a Code type. - name: indent_type type: string description: | Choose between `tabs` and `spaces`. Choose wisely. Default: `tabs`. - name: indent_size type: integer description: | Set your preferred indentation size (in spaces). Default: `4`. - name: line_numbers type: boolean description: | Show line numbers. - name: line_wrapping type: boolean description: | Enable to wrap long lines of code instead of showing a horizontal scroll. Default: `true`. - name: key_map type: string description: | Pick your preferred set of keyboard shortcuts. Choose between `default`, `sublime`, and `vim`. We'll let you guess which one is default. - name: rulers type: array description: | You can set the columns and the line style (choose between `dashed` or `solid`) of any rulers you wish to use. id: 3ca28569-5b86-49a1-b620-ea3364561cde --- ## Overview If your content involves code snippets, this is the fieldtype for you. It's a [CodeMirror](https://codemirror.net) field with 25 of the most common languages ready for highlighting, handles tabs and spaces, has a dark mode, and best of all — for you super nerds out there — a vim key binding. ## Data Structure The code fieldtype stores a string. Do whatever you'd like with it. ``` yaml code_snippet: | {{ code_snippet }} ``` ::tab blade ```blade
{!! $code_snippet !!}
``` :: You're also able to use it as an array if you want to output the mode. ::tabs ::tab antlers ``` {{ code_snippet }}
{{ code }}
{{ /code_snippet }} ``` ::tab blade ```blade
{!! $code_snippet['code'] !!}
``` :: ### Variables Inside an code fieldtype's tag pair you'll have access to the following variables. | Variable | Description | |----------|-------------| | `code` | The contents of the field. | | `mode` | The selected language mode. | ================================================ FILE: content/collections/fieldtypes/collections.md ================================================ --- title: Collections extends: 9dd58c40-6e33-49c8-83fa-61a69f6371be description: Choose from one or more collections. overview: Allows you to choose one or more collections. options: - name: max_items type: integer description: > The maximum number of items that may be selected. Setting this to `1` will change the UI to a dropdown. screenshot: /fieldtypes/screenshots/collections.png id: 44c3da60-ef47-408e-afc4-a33026c86f5d --- ## Usage This fieldtype is used to view and select from a list of Collections. ```yaml fields: my_collections_field: type: collections ``` ## Data Structure The Collections fieldtype is a [Relationship fieldtype](/relationships#fieldtypes), and will save the collections as their handles (the folder name). ```yaml listings: - blog - things ``` ## Templating You're more than likely using this field as a way to dynamically display a collection. ::tabs ::tab antlers Since the collection tag accepts a pipe-delimited list of collection names, you can join them together like this: ```antlers
    {{ collection from="{listings|piped}" }}
  • {{ title }}
  • {{ /collection }}
``` ::tab blade You can pass the values from the collections fieldtype to the collection tag like so: ```blade
  • {{ $title }}
``` :: ```html
  • A blog entry
  • A thing entry
  • etc
``` ================================================ FILE: content/collections/fieldtypes/color.md ================================================ --- title: Color description: 'Manage colors by hex code with swatches and text inputs.' intro: 'A simple color picker with support for pre-defined swatches as well as entering a color by hex code.' screenshot: fieldtypes/screenshots/v6/color.webp screenshot_dark: fieldtypes/screenshots/v6/color-dark.webp options: - name: swatches type: array description: | Pre-define colors that can be selected from a list. Supports all color mode formats. - name: allow_any type: boolean description: | Allow entering any color value via picker or hex code. id: 09b4af2d-265a-49ee-ac74-3e27041c180b --- ## Overview If you want work with colors, this is the way to do it. You could combine it with [Bard](/fieldtypes/bard) or [Replicator](/fieldtypes/replicator) to create page "page builders", use it to choose background colors for headers or hero blocks, or even image overlays with `mix-blend-mode: multiply`. Go get creative! ## Data Structure The color fieldtype stores the color values as a hex string. ``` yaml header_color: "#FF269E" ``` ## Templating The color is output as a simple string. Most often you'll use this in an inline `style` tag to style elements of your front-end site. ::tabs ::tab antlers ```antlers

Bay Side High's Sweetheart Dance

This Friday Night!

``` ::tab blade ```blade

Bay Side High's Sweetheart Dance

This Friday Night!

``` :: ================================================ FILE: content/collections/fieldtypes/date.md ================================================ --- title: Date description: Helps you pick a date, but not get one. intro: > Work with dates, times, and ranges with a variety of user interface options that make you really enjoy basically just picking numbers from a table. screenshot: fieldtypes/screenshots/v6/date.webp screenshot_dark: fieldtypes/screenshots/v6/date-dark.webp options: - name: columns type: integer description: | Show multiple months at one time, in columns and rows. Default: `1`. - name: earliest_date type: string description: | Set the earliest selectable date in `YYYY-MM-DD` format. Default: `1900-01-01`. - name: format type: string description: | How the date should be stored, using the [PHP date format](https://www.php.net/manual/en/datetime.format.php). We recommend choosing a format which stores date & time. - name: full_width type: boolean description: | Enable to stretch the calendar out like Stretch Armstrong, using the maximum amount of available horizontal space. Default: `false`. - name: inline type: boolean description: | Always show the calendar instead of the text input and dropdown UI. Default: `false`. - name: mode type: string description: | Choose between `single` or `range`. Range mode disables the time picker. Default: `single`. - name: rows type: integer description: | Show multiple months at one time, in columns and rows. Default: `1`. - name: time_enabled type: boolean description: | Enable/disable the timepicker. Default: `false`.
Date fieldtype with time enabled Date fieldtype with time enabled
Now you can pick a time, too!
- name: time_required type: boolean description: | Makes the time field visible and non-dismissible. Default: `false`. - name: timezone type: string description: | The timezone dates will be displayed and entered in within the Control Panel. Accepts any [IANA timezone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) (e.g. `America/New_York`). Defaults to the site-wide [`default_timezone`](#control-panel-timezone) configured in `config/statamic/cp.php`, which itself defaults to `auto` (the browser's local timezone). id: 7dfba904-8a74-40e1-b507-51cd2b5f6123 --- ## Overview Date fields have highly configurable user interfaces. They can be as simple as a single date and/or time, or as fancy as a multi-month calendar with multi-day range picking. Be sure to experiment with the various config [options](#options) to create the best experience for your content authors. ## Data Structure Single dates are stored as a date/timestring. Ranges are stored as an array with a `start` and `end` key. ``` yaml date: 1983-10-01 12:00:00 date_range: start: 2019-11-18 00:00 end: 2019-11-22 00:00 ``` Dates are stored in your application's timezone. The time will be when `time_enabled` is `true`, or depending on the timezone of the user who selected the date. e.g. On date fields where there is no time configured, it will assume midnight for the person who selected it. ## Templating Date fields are [augmented](/augmentation) to return a [Carbon instance][carbon]. When used as a string they will return a pre-formatting output that uses your `config.date` format setting. By default that'll look like `January 1, 2020`. ### Date Ranges Ranges have nested `start` and `end` variables, so you can access them like this: ::tabs ::tab antlers ```antlers // Nested variable Event: {{ date:start }} through {{ date:end }} // Tag pair {{ date }} Event: {{ start }} through {{ end }} {{ /date }} ``` ::tab blade ```blade Event: {{ $date_range['start'] }} through {{ $date_range['end'] }} ``` ::
Date fieldtype in range mode Date fieldtype in range mode
Ranges are much simpler than two date fields.
### Formatting Dates You can format the output of your date fields with the [format modifier](/modifiers/format) and PHP's [date formatting options](https://www.php.net/manual/en/function.date.php). ::tabs ::tab antlers ```antlers {{ date format="Y" }} // 2019 {{ date format="Y-m-d" }} // 2019-10-10 {{ date format="l, F jS" }} // Sunday, January 21st ``` ::tab blade When using Blade, you may also call the `->format` method on Carbon instances. ```blade {{-- Using Modifiers --}} {{ Statamic::modify($date)->format('Y') }} // 2019 {{ Statamic::modify($date)->format('Y-m-d') }} // 2019-10-10 {{ Statamic::modify($date)->format('l, F jS') }} // Sunday, January 21st {{-- Using Carbon methods --}} {{ $date->format('Y') }} // 2019 {{ $date->format('Y-m-d') }} // 2019-10-10 {{ $date->format('l, F jS') }} // Sunday, January 21st ``` :: ### Formatting localized Dates You can format localized dates with the [iso modifier](/modifiers/iso_format) and [ISO formatting options](https://carbon.nesbot.com/docs/#api-localization). This use Carbon's inner translations rather than language packages you need to install on every machine where you deploy your application. ::tabs ::tab antlers ```antlers {{ date iso_format="YYYY" }} // 2019 {{ date iso_format="YYYY-MM-DD" }} // 2019-10-10 {{ date iso_format="dddd, MMMM Do" }} // Sunday, January 21st ``` ::tab blade When using Blade, you may also call the `->isoFormat` method on Carbon instances. ```blade {{-- Using Modifiers --}} {{ Statamic::modify($date)->isoFormat('YYYY') }} // 2019 {{ Statamic::modify($date)->isoFormat('YYYY-MM-DD') }} // 2019-10-10 {{ Statamic::modify($date)->isoFormat('dddd, MMMM Do') }} // Sunday, January 21st {{-- Using Carbon methods --}} {{ $date->isoFormat('YYYY') }} // 2019 {{ $date->isoFormat('YYYY-MM-DD') }} // 2019-10-10 {{ $date->isoFormat('dddd, MMMM Do') }} // Sunday, January 21st ``` :: ## Timezones Dates are stored in your application timezone, then converted before being displayed to users. For more information on how Statamic handles timezones, please review our [Timezones](/tips/timezones) guide. ### Control Panel Timezone By default, dates in the Control Panel are displayed and entered in the browser's local timezone. This means a user in New York and a user in London editing the same entry would each see the date in their own timezone. If you'd prefer all users to see and enter dates in a specific timezone, you can configure it site-wide in `config/statamic/cp.php`: ```php 'default_timezone' => env('STATAMIC_CP_DEFAULT_TIMEZONE', 'auto'), ``` Set this to any [IANA timezone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) (e.g. `America/New_York`) to pin all date fields to that timezone. The default value of `auto` uses each user's browser timezone. You can also override this on a per-field basis with the [`timezone`](#options) field config option. [carbon]: https://carbon.nesbot.com/docs/ ================================================ FILE: content/collections/fieldtypes/dictionary.md ================================================ --- title: Dictionary description: Choose from options provided by dictionaries. intro: Give your users a list of options to choose from. Similar to the Select field, but allows you to read options from YAML or JSON files, or even hit external APIs. screenshot: fieldtypes/screenshots/v6/dictionary.webp screenshot_dark: fieldtypes/screenshots/v6/dictionary-dark.webp options: - name: dictionary type: array description: | Configure the dictionary to be used. You may also define any config values which should be passed along to the dictionary. The `dictionary` option accepts both string & array values: ```yaml # When it's a dictionary without any config fields... dictionary: countries # When it's a dictionary with config fields... dictionary: type: countries region: Europe ``` - name: placeholder type: string description: | Set the non-selectable placeholder text. Default: none. - name: default type: string description: | Set the default option key. Default: none. - name: max_items type: integer description: > Cap the number of selections. Setting this to 1 will change the UI. Default: null (unlimited). id: 9b14b5b8-6a7a-4db2-8533-9c78faa0e054 --- ## Overview At a glance, the Dictionary fieldtype is similar to the [Select fieldtype](/fieldtypes/select). However, with the Dictionary fieldtype, options aren't manually defined in a field's config, but rather returned from a PHP class (called a "dictionary"). This can prove to be pretty powerful, since it means you can read options from YAML or JSON files, or even hit an external API. It also makes it easier to share common select options between projects. ## Data Storage Dictionary fields will store the "key" of the chosen option or options. For example, a dictionary might have items such as: ```php 'jan' => 'January', 'feb' => 'February', 'mar' => 'March', ``` Your saved data will be: ``` yaml select: jan ``` ## Templating Dictionary fields will return the "option data" returned by the dictionary's `get` method. The shape of this data differs between dictionaries and is outlined below. For example, using the built-in Countries dictionary, your template might look like this: ```yaml past_vacations: - USA - AUS - CAN - DEU - GBR ``` ::tabs ::tab antlers ```antlers
    {{ past_vacations }}
  • {{ emoji }} {{ name }}
  • {{ /past_vacations }}
``` ::tab blade ```blade
    @foreach ($past_vacations as $vacation)
  • {{ $vacation['emoji'] }} {{ $vacation['name'] }}
  • @endforeach
``` :: ```html
  • 🇺🇸 United States
  • 🇦🇺 Australia
  • 🇨🇦 Canada
  • 🇩🇪 Germany
  • 🇬🇧 United Kingdom
``` ## Available Dictionaries Statamic includes a few dictionaries straight out of the box. ### File This allows you point to a file located in your `resources/dictionaries` directory to populate the options. The file can be `json`, `yaml`, or `csv`. Each option array should have `label` and `value` keys at the minimum. Any additional keys will be available when templating. You may redefine which keys are used for the labels and values by providing them to your fieldtype config. In the following example, `name` is the label and `id` is the value. ```json [ {"name": "Apple", "id": "apple", "emoji": "🍎"}, {"name": "Banana", "id": "banana", "emoji": "🍌"}, {"name": "Cherry", "id": "cherry", "emoji": "🍒"}, ... ] ``` ```yaml - handle: fruit field: type: dictionary dictionary: type: file filename: fruit.json label: name # optional, defaults to "label" value: id # optional, defaults to "value" ``` You may provide enhanced labels using basic Antlers syntax. For example, to include the emoji before the fruit name, you can do this: ```yaml label: '{{ emoji }} {{ name }}' ``` ### Countries This provides a list of countries with their ISO codes, region, subregion, and flag emoji. ```yaml - handle: countries field: type: dictionary dictionary: type: countries region: 'oceania' # Optionally filter the countries by a region. # Supported options are: africa, americas, asia, europe, oceania, polar emojis: true # Whether flag emojis are in the labels. They're on by default. ``` ```yaml countries: - USA - AUS ``` ::tabs ::tab antlers ```antlers {{ countries }} {{ emoji }} {{ name }}, {{ iso2 }}, {{ iso3 }}, {{ region }}, {{ subregion }} {{ /countries }} ``` ::tab blade ```blade @foreach ($countries as $country) {{ $country['emoji'] }} {{ $country['name'] }}, {{ $country['iso2'] }}, {{ $country['iso3'] }}, {{ $country['region'] }}, {{ $country['subregion'] }} @endforeach ``` :: ``` 🇺🇸 United States, US, USA, Americas, Northern America 🇦🇺 Australia, AU, AUS, Oceania, Australia and New Zealand ``` ### Timezones This provides a list of timezones and their UTC offsets. ```yaml - handle: timezones field: type: dictionary dictionary: type: timezones ``` ```yaml timezones: - America/New_York - Australia/Sydney ``` ::tabs ::tab antlers ```antlers {{ timezones }} {{ name }} {{ offset }} {{ /timezones }} ``` ::tab blade ```blade @foreach ($timezones as $timezone) {{ $timezone['name'] }} {{ $timezone['offset'] }} @endforeach ``` :: ``` America/New_York -04:00 Australia/Sydney +10:00 ``` ### Currencies This provides a list of currencies, with their codes, symbols, and decimals. ```yaml - handle: currencies field: type: dictionary dictionary: type: currencies ``` ```yaml currencies: - USD - HUF ``` ::tabs ::tab antlers ```antlers {{ currencies }} {{ name }}, {{ code }}, {{ symbol }}, {{ decimals }} {{ /currencies }} ``` ::tab blade ```blade @foreach ($currencies as $currency) {{ $currency['name'] }}, {{ $currency['code'] }}, {{ $currency['symbol'] }}, {{ $currency['decimals'] }} @endforeach ``` :: ``` US Dollar, USD, $, 2 Hungarian Forint, HUF, Ft, 0 ``` ## Custom Dictionaries In many cases, using the native [File](#file) dictionary can be all you need for something custom. However, it's possible to create an entirely custom dictionary that could read from files, APIs, or whatever you can think of. [Find out how to create a custom dictionary](/extending/dictionaries) ================================================ FILE: content/collections/fieldtypes/entries.md ================================================ --- title: Entries meta_title: 'Entries Fieldtype' description: 'Create relationships with other entries.' intro: | Create relationships with other entries in one or more collections. It's not very much like online dating because you can create and link the entries on the fly without leaving the page. screenshot: fieldtypes/screenshots/v6/entries.webp screenshot_dark: fieldtypes/screenshots/v6/entries-dark.webp options: - name: collections type: array description: | Configure which collections you want to allow relationships with. - name: create type: boolean description: | By default you may create new entries. Set to `false` to only allow selecting from existing entries. Only available in the `default` mode. - name: max_items type: integer description: > The maximum number of items that may be selected. Setting this to `1` will change the UI to a dropdown. - name: mode type: string description: | Set the UI style for this field. Can be one of 'default' (Stack Selector), 'select' (Select Dropdown) or 'typeahead' (Typeahead Field). - name: query_scopes type: string description: > Allows you to specify a [query scope](/extending/query-scopes-and-filters#scopes) which should be applied when retrieving selectable entries. You should specify the query scope's handle, which is usually the name of the class in snake case. For example: `MyAwesomeScope` would be `my_awesome_scope`. - name: search_index type: string description: > Allows you to specify a [search index](/search#indexes) to be used when searching for entries. - name: select_across_sites type: boolean description: | When enabled, entries from all sites will be displayed. id: acee879a-c832-449d-a714-c57ea5862717 --- ## Overview Use this fieldtype to create a one-way relationship with entries of any collection in your site. It's delightfully simple. :::watch https://youtube.com/embed/WWbsM5u9afc Watch how to build a "Related Articles" feature using the Entries Fieldtype ::: ## Data Structure This fieldtype will store an array of ids to the selected entries. They will be augmented in your Antlers templates to give you access to each entry's data. ``` yaml related_entries: - 12f9be1f-a12e-4680-b769-639d2d1f1d14 - ea48926d-bf67-4d45-9420-9627a31c37fb ``` ## Templating Loop through the entries and do anything you want with the data. ::tabs ::tab antlers ```antlers
    {{ related_entries }}
  • {{ title }}
  • {{ /related_entries }}
``` ::tab blade ```blade ``` :: ```html ``` ================================================ FILE: content/collections/fieldtypes/float.md ================================================ --- title: Float description: 'For when all you want is decimal numbers.' intro: 'The float fieldtype is a text-style input that only accepts floats (numbers) and has increment and decrement controls.' screenshot: fieldtypes/screenshots/v6/float.webp screenshot_dark: fieldtypes/screenshots/v6/float-dark.webp id: 3dcf9495-9a02-4044-b6bb-428b7ff23807 options: - name: append type: string description: > Add text after (to the right of) the float input. - name: prepend type: string description: > Add text to the beginning (to the left of) the float input. - name: placeholder type: int description: > Set a default placeholder value. - name: min type: int description: > Set the minimum allowed value. - name: max type: int description: > Set the maximum allowed value. - name: step type: int description: > Set the interval between valid numbers. --- ## Overview The float fieldtype is essentially an HTML5 input with `type="number"`. Very similar to the [Integer fieldtype](/fieldtypes/integer), but it allows decimal numbers. ## Data Storage Stores a float – a decimal number. ================================================ FILE: content/collections/fieldtypes/form.md ================================================ --- id: d630ea15-d94f-4404-84d2-0926a898e672 blueprint: fieldtype title: Form screenshot: fieldtypes/screenshots/v6/form.webp screenshot_dark: fieldtypes/screenshots/v6/form-dark.webp description: 'Pick a form, any form.' overview: | Use this fieldtype to create a relationship with one of your site's [forms](/forms). options: - name: max_items type: integer required: false description: 'The maximum number of forms that may be selected.' - name: placeholder type: string description: | Set the non-selectable placeholder text. Default: none. - name: query_scopes type: string description: > Allows you to specify a [query scope](/extending/query-scopes-and-filters#scopes) which should be applied when retrieving selectable assets. You should specify the query scope's handle, which is usually the name of the class in snake case. For example: `MyAwesomeScope` would be `my_awesome_scope`. related_entries: - fdb45b84-3568-437d-84f7-e3c93b6da3e6 - aa96fcf1-510c-404b-9b63-cea8942e1bf8 --- ## Overview The Form fieldtype is gives your users a way to pick a form to include along with the current entry. How that form is implemented or shows up on the page is up to you. ## Data Storage The Form fieldtype stores the `handle` of a single form as a string, or an array of handles if `max_items` is greater than 1. ## Templating The Form fieldtype provides a few useful variables: * `handle` * `title` * `fields` * `api_url` * `honeypot` You can use the [`form:create`](/tags/form-create) tag to render a `
` on your page. ================================================ FILE: content/collections/fieldtypes/grid.md ================================================ --- title: Grid description: Manage columns of dynamic rows of data that can contain any other fieldtypes. overview: > The grid fieldtype is a _meta_ fieldtype, a fieldtype that serves as a container for more fieldtypes. Any fieldtypes. Think of Grid as a spreadsheet, where each column contains any fieldtype, _including another Grid_. We lovingly refer to these as Inception Grids. Let's go deeper. screenshot: fieldtypes/screenshots/v6/grid.webp screenshot_dark: fieldtypes/screenshots/v6/grid-dark.webp options: - name: min_rows type: 'integer *0*' description: The minimum number of required rows. - name: max_rows type: integer description: > The maximum number of rows allowed. Once reached the `Add Row` button will disappear. - name: fields type: array description: > A list of fields, each of which create their own column. - name: mode type: string *table* description: > The Grid is displayed as a table by default. If you have a large number of columns it can get pretty crowded. Choose `stacked` mode to group rows similar to [Replicator](/fieldtypes/replicator). When [Sneak Peek]() is enabled, Grids automatically toggle into stacked mode. - name: add_row type: string description: "The `Add Row` button's label." - name: reorderable type: string description: "Enable row reordering. Default: `true`." id: fa6d2032-0e42-4ea5-b20c-4226941bf0da --- ## Fieldtypes You can use any fieldtypes inside a Grid. Just remember that because you can doesn't mean you should. Your UI experience will vary greatly. Make sure to compare the experience with the other meta-fields: [Replicator](/fieldtypes/replicator) and [Bard](/fieldtypes/bard). ## Data Structure The Grid field creates a YAML collection (associative array). ## Templating The example below would have the following data which can be looped through as a tag pair with access to the column data as variables. ```yaml cast: - actor: Mark Hamill character: Luke Skywalker - actor: Harrison Ford character: Han Solo ``` ::tabs ::tab antlers ```antlers

Star Wars Cast

    {{ cast }}
  • {{ character }} played by {{ actor }}
  • {{ /cast }}
``` ::tab blade ```blade

Star Wars Cast

    @foreach ($cast as $role)
  • {{ $role->character }} played by {{ $role->actor }}
  • @endforeach
``` :: ```html

Star Wars Cast

  • Luke Skywalker played by Mark Hamill
  • Han Solo played by Harrison Ford
``` ================================================ FILE: content/collections/fieldtypes/group.md ================================================ --- id: 9780cee8-0732-40b5-bc71-ed846d0c290d blueprint: fieldtype title: Group description: 'Group fields visually and scoped their own key in the data.' overview: | Organize the data by visually grouping related fields and assigning a distinct key to each group for clearer data structuring. screenshot: fieldtypes/screenshots/v6/group.webp screenshot_dark: fieldtypes/screenshots/v6/group-dark.webp --- ## Overview A group fieldtype is a simple container that holds additional fields you would like grouped visually as well as under a parent key. ## Data Storage In the screenshot above, the data structure for these fields will be as follows: ```yaml location: address: 123 Main Street city: Schenectady zip: 12345 ``` ## Templating All fields inside a Group will be scoped under their parent key like so: ::tabs ::tab antlers ```antlers {{ location:address }}, {{ location:city }}, {{ location:zip }} ``` ::tab blade ```blade {{ $location['address'] }}, {{ $location['city'] }}, {{ $location['zip'] }} ``` :: will output ```html 123 Main Street, Schenectady, 1234 ``` ================================================ FILE: content/collections/fieldtypes/hidden.md ================================================ --- title: Hidden description: Set default data when creating new entries. overview: "The hidden field is perfect for setting default data when creating new entries. Set anything as the `default` field value and you're good to go." id: 791c3fb3-0d3c-4e17-bd97-3ab9529a8691 --- ## Overview This is as simple as you get. Set a default value and it it'll be stored when creating new entries. This is useful if you want to set specific data but don't want it editable in the Control Panel. ``` yaml handle: snappy_comeback field: type: hidden default: Eat my shorts ``` That's pretty much it. Nothing else to see here. ================================================ FILE: content/collections/fieldtypes/html.md ================================================ --- title: HTML meta_title: 'HTML Fieldtype' description: 'Add a little presentation-only HTML to your blueprint.' intro: | If you've ever wanted to add a little HTML to your blueprint, this is the way to do it. Longer instructions, images, embedded help videos — if you can write it, you can...write it. screenshot: fieldtypes/screenshots/v6/html.webp screenshot_dark: fieldtypes/screenshots/v6/html-dark.webp options: - name: html type: string description: "Store whatever HTML you want — it's up to you." id: 55e0bd1d-4880-42ee-9a09-c4ece62f6483 --- ## Data Structure This fieldtype is presentation-only and stores no data. ## Templating This fieldtype is presentation-only and has no function or purpose on the frontend. Kind of like neckties. ================================================ FILE: content/collections/fieldtypes/icon.md ================================================ --- id: 50ed54f8-18b3-4b46-b0d7-6fedc07ad81f blueprint: fieldtype title: Icon description: 'Simple UI to select SVG icons from a dropdown.' intro: 'Give your users a list of icons to choose from. This field supports search and keyboard commands, and can be configured to use your own icons or ones managed by Statamic.' screenshot: fieldtypes/screenshots/v6/icon.webp screenshot_dark: fieldtypes/screenshots/v6/icon-dark.webp options: - id: nKVDUK7I name: set type: string description: "Name of a custom icon set. Uses Statamic's icon set by default." required: false - id: u8yCCuXb name: default type: string description: 'Set the default option key. Default: none.' required: false --- ## Overview The Icon field allows you to easily select icons from the Control Panel's default list, or define the path to the directory and/or folder of your own that contains SVG icons. It has a very minimal UI which will help streamline your Blueprints and help authors build pages faster with less clicks. ## Templating Icon fields return inline string of the selected SVG icon. ::tabs ::tab antlers ```antlers {{ icon }} ``` ::tab blade ```blade {!! $icon !!} ``` :: ```html ``` ## Icon Sets By default, the Icon fieldtype uses Statamic's built-in icons. However, you can register a custom icon set in your `AppServiceProvider` and specify it in the field's config. ```php // AppServiceProvider.php use Statamic\Facades\Icon; // [tl! ++] Icon::register('heroicons', base_path('resources/heroicons')); // [tl! ++] ``` ```yaml - handle: favourite_icon field: type: icon set: heroicons # [tl! ++] ``` ================================================ FILE: content/collections/fieldtypes/integer.md ================================================ --- title: Integer description: 'For when all you want is numbers.' intro: 'The integer fieldtype is a text-style input that only accepts integers (numbers) and has increment and decrement controls.' screenshot: fieldtypes/screenshots/v6/integer.webp screenshot_dark: fieldtypes/screenshots/v6/integer-dark.webp id: 4038c2ac-8c3a-4f4c-8530-8c5f9c8242a6 options: - name: append type: string description: > Add text after (to the right of) the integer input. - name: prepend type: string description: > Add text to the beginning (to the left of) the integer input. - name: placeholder type: int description: > Set a default placeholder value. - name: min type: int description: > Set the minimum allowed value. - name: max type: int description: > Set the maximum allowed value. - name: step type: int description: > Set the interval between valid numbers. --- ## Overview The integer fieldtype is essentially an HTML5 input with `type="number"`. Using the `up` and `down` keyboard keys will increment and decrement the value by `1`. ## Data Storage Stores an integer – a whole number that is not a fraction. ================================================ FILE: content/collections/fieldtypes/link.md ================================================ --- title: Link description: 'Create links to URLs, entries, or child entries.' intro: | A select box gives you the option to choose what type of link you'd like to create. When set to URL it gives you a text box to enter the hyperlink. When set to Entry it opens a stack with all your entries to choose from. And when set to First Child will redirect a visitor to the first child page in a structure. screenshot: fieldtypes/screenshots/v6/link.webp screenshot_dark: fieldtypes/screenshots/v6/link-dark.webp id: 69975d6f-760e-4ce4-a92b-d98e122744a8 options: - name: collections type: array description: | Configure which collections you want to allow relationships with. - name: container type: string description: > An asset container ID. When specified, the fieldtype will allow the user to add a link to an asset from the specified container. --- ## Overview For when you want to create a link to a URL or entry, this fieldtype is here for you. We often see it used in [Grids](/fieldtypes/grid) and [Replicators](/fieldtypes/replicator). ## Data Storage ``` yaml --- url_link: 'https://statamic.com' entry_link: 'entry::9d682ce3-a353-4fdd-af5e-c1d21b7a87f7' first_child_link: '@child' ``` ## First Child Creating a "first child" link will dynamically return the URL to first entry nested below in a [Structure](/structures) or [Navigation](/navigation). For example, if you set a First Child link on the Getting Started entry below, it will return the URL to the "Requirements" entry.
A Statamic 6 structure tree A Statamic 6 structure tree
This option will only be provided when the field is in a collection. Globals and terms, by their nature, don't have children. ## Templating Link fields will render a URL string you can use however you choose. ::tabs ::tab antlers ```antlers Check out Statamic! ``` ::tab blade ```blade Check out Statamic! ``` :: ```output Check out Statamic! ``` You can access other data of the link field by using it like an array. This could be the title of an entry you link to, for example. ::tabs ::tab antlers ```antlers {{ link_field:title }} ``` ::tab blade ```blade {{ $link_field['title'] }} ``` :: ================================================ FILE: content/collections/fieldtypes/list.md ================================================ --- title: List description: Manage simple lists with the help of a keyboard-friendly interface. intro: > Create YAML lists with a robust user interface. It has full keyboard controls so you can use `up` to go up, `down` to go down, drag and drop to rearrange the order, and click an item to select it and begin editing. screenshot: fieldtypes/screenshots/v6/list.webp screenshot_dark: fieldtypes/screenshots/v6/list-dark.webp id: bd079cba-c5d2-475d-ae82-57874818858e --- ## Overview For when you want to manage a simple YAML list, this fieldtype is here for you. Being able to reorder the list items is nice, as is the ability to delete them. ## Data Storage ``` yaml product_ideas: - 'Knife-Wrench (for kids!)' - 'Kite-Fork' - 'Apple-Cranberry hybrid (calling it Appleberry™)' ``` ## Templating Loop through the array items to display each item's `value`. ::tabs ::tab antlers ```antlers

Product Ideas

    {{ product_ideas }}
  • {{ value }}
  • {{ /product_ideas }}
``` ::tab blade ```blade

Product Ideas

    @foreach ($product_ideas as $idea)
  • {{ $idea }}
  • @endforeach
``` :: ```html

Product Ideas

  • Knife-Wrench (for kids!)
  • Kite-Fork
  • Apple-Cranberry hybrid (calling it Appleberry™)
``` ================================================ FILE: content/collections/fieldtypes/markdown.md ================================================ --- title: Markdown description: Our beautiful Markdown editor with preview, assets integration, and more. intro: Write Markdown with the help of formatting buttons, assets integration, fullscreen mode, a Markdown cheatsheet, and HTML preview mode. What more do you need? screenshot: fieldtypes/screenshots/v6/markdown.webp screenshot_dark: fieldtypes/screenshots/v6/markdown-dark.webp id: 607cfe62-7239-461b-8f55-8e7a312c2d5d related_entries: - be292d2b-dc0e-48dc-bce4-0058df27ccc6 options: - name: antlers type: string description: > Enable Antlers parsing in this field's content. - name: automatic_line_breaks type: boolean description: > Automatically convert line breaks to `<br>` tags. Default: `true`. - name: automatic_links type: boolean description: > Automatically links any URLs in the text. Default: `false`. - name: container type: string description: | Set the name of an [asset container](/assets#containers) to enable browsing, uploading, and inserting assets. - name: escape_markup type: boolean description: > Escapes inline HTML markup. For example, `<div>` will be replaced with `&lt;div&gt;`. Default: `false`. - name: folder type: string description: | The folder (relative to the container) to begin browsing. Default: the root folder of the container. - name: heading_anchors type: boolean description: | Inject anchor links to all of your heading elements (`<h1>`, `<h2>`, etc). Default: `false`. - name: parser type: string description: > The name of a customized Markdown parser. Leave blank for default. - name: restrict type: bool description: > If `true`, navigation within the asset browser will be disabled. Your users will be restricted to specified the container and folder. Default: `false`. - name: smartypants type: boolean description: > Automatically convert straight quotes into curly quotes, dashes into en/em-dashes, and other similar text transformations. Default: `false`. - name: table_of_contents type: boolean description: > Automatically insert a table of contents at the top of your content with links to your headings. Default: `false`. --- ## Overview Markdown has been around since 2004. One fateful day in December, [John Gruber](https://daringfireball.net/projects/markdown/) published his spec and first version of the Markdown parser. Since that day (it was a Friday), Markdown has grown wildly in popularity, and today has become the de facto standard format for writing portable content. Back in 2004 there was just one flavor: John's. Today's landscape has many variations, parsers, extensions, and standards groups. The most widely accepted feature set is [Github-Flavored Markdown][gfm], or GFM for short. Statamic uses the [League\CommonMark][commonmark] library to support GFM, to enable tables, special attributes like classes and ids on block-level elements, and fenced code blocks. ## Data Structure The data will be saved exactly as written – a Markdown string. ``` markdown ## Overview This is the Markdown fieldtype. It's for writing [Markdown](https://daringfireball.net/projects/markdown/), an easy-to-read, easy-to-write plain text format that magically transforms into HTML. ``` ## Templating The Markdown content will be automatically transformed into HTML through [augmentation](/augmentation). You need only use the variable and the rest is done for you. ::tabs ::tab antlers ```antlers {{ content }} ``` ::tab blade ```blade {!! $content !!} ``` :: ```html

Overview

This is the Markdown fieldtype. It's for writing Markdown, an easy-to-read, easy-to-write plain text format that magically transforms into HTML.

``` [commonmark]: https://commonmark.thephpleague.com/ [gfm]: https://help.github.com/en/categories/writing-on-github ================================================ FILE: content/collections/fieldtypes/navs.md ================================================ --- title: Navs meta_title: 'Navs Fieldtype' description: Choose from one or more navigations. overview: Allows you to choose from one or more navigations. screenshot: fieldtypes/screenshots/v6/navs.webp screenshot_dark: fieldtypes/screenshots/v6/navs-dark.webp options: - name: max_items type: integer description: > The maximum number of items that may be selected. Setting this to `1` will change the UI to a dropdown. - name: mode type: string description: | Set the UI style for this field. Can be one of 'default' (Stack Selector), 'select' (Select Dropdown) or 'typeahead' (Typeahead Field). - name: structure_types type: array description: > Configure which types of structures you want to be selectable. Options are `collection` or `navigation`. id: 0669f781-bc12-44ff-bbbb-921b80aaf4f3 --- ## Overview Use this fieldtype to create a one-way relationship with one or more navigations in your site. It's a simple-little-helper type of thing. ## Data Structure The Navs fieldtype stores the `handle` of a single navigation as a string, or an array of handles if `max_items` is greater than 1. ``` yaml navigations: - main_nav - footer ``` ## Templating Loop through the structures to access their handles and pass them to a [Nav](/tags/nav) tag. ::tabs ::tab antlers ```antlers {{ navigations }} {{ /navigations }} ``` ::tab blade ```blade @foreach ($navigations as $nav) @endforeach ``` :: ```html ``` ================================================ FILE: content/collections/fieldtypes/radio.md ================================================ --- title: Radio description: 'Circles you click. You can only choose one.' intro: | Radio buttons. The "you can only have one" variation of checkboxes. Create some options and let your users select one and only one. May they choose wisely. screenshot: fieldtypes/screenshots/v6/radio.webp screenshot_dark: fieldtypes/screenshots/v6/radio-dark.webp options: - name: inline type: bool description: | Show the radio buttons next to each other in a row instead of stacked vertically. Default: `false` - name: options type: array description: 'Sets of key/value pairs define the values and labels of the radio options.' id: 0b662f17-1cd1-4c64-a705-980a2ca5aab4 --- ## Overview The radio fieldtype is a multiple choice input where you only get one choice. It saves the chosen option from a preset list. ## Configuring Use the `options` setting to define a list of values and labels. ``` yaml favorite: type: radio instructions: Choose your favorite food. options: donuts: Donuts icecream: Ice Cream brownies: Brownies ``` You may omit the labels and just specify keys. If you use this syntax, the value and label will be identical. ``` yaml options: - Donuts - Ice Cream - Brownies ``` ### Options in blueprint YAML See [Select · Options in blueprint YAML](/fieldtypes/select#options-in-blueprint-yaml)—checklists defined in the blueprint use the same expanded `key` / `value` option rows for ordered, storage-agnostic option lists. ## Data Structure The chosen option is stored as a string. If you only specified values for the `options` array, then the label will be saved. ``` yaml favorite: brownies ``` ## Templating It's a string, so you can just use that value. ::tabs ::tab antlers ```antlers

I love {{ favorite }}. A lot.

``` ::tab blade ```blade

I love {{ $favorite }}. A lot.

``` :: ```html

I love donuts. A lot.

``` ================================================ FILE: content/collections/fieldtypes/range.md ================================================ --- title: Range description: 'Choose a number between a min and max value.' intro: | Range fields let the user choose a numeric value which must be _no less_ than a given value, and _no more_ than another. screenshot: fieldtypes/screenshots/v6/range.webp screenshot_dark: fieldtypes/screenshots/v6/range-dark.webp options: - name: min type: integer description: | The minimum, left-most value. Default `0`. - name: max type: integer description: | The maximum, left-most value. Default `1000`. - name: step type: integer description: | The minimum size between values. Default `1`. - name: append type: string description: | Add text to the end (right-side) of the range slider. - name: prepend type: string description: | Add text to the beginning (left-side) of the range slider. id: 5ede219c-607e-4ad2-8498-6ca55a063e73 --- ## Data Structure The value is stored as an integer. ``` yaml number: 42 ``` ## Templating Use the variable in your templates to display the value. That's pretty much it. ::tabs ::tab antlers ```antlers

My favorite number is {{ number }}.

``` ::tab blade ```blade

My favorite number is {{ $number }}.

``` :: ```html

My favorite number is 42.

``` ================================================ FILE: content/collections/fieldtypes/replicator.md ================================================ --- title: Replicator description: Build your content by creating sets of fields you can mix and match on the fly. overview: | The Replicator is a meta fieldtype giving you the ability to define _sets_ of fields that you can dynamically piece together in whatever order and arrangement you imagine. You can build long-form articles like [Medium.com](http://medium.com) and take advantage of the extra markup control. It's so much better than a WYSIWYG field. screenshot: fieldtypes/screenshots/v6/replicator.webp screenshot_dark: fieldtypes/screenshots/v6/replicator-dark.webp options: - name: sets type: array description: An array containing sets of fields. - name: max_sets type: integer description: The maximum number of sets that may be added. - name: button_label type: string description: Allows you to define a label for the "Add Set" button. id: 00b140e3-413a-4d91-b9e7-65f58d56a41b --- ## Usage You will be presented with a button for each set you’ve defined. Clicking one will replicate an empty set. You can [replicate](https://www.youtube.com/watch?v=qD4EVXkfe0w) a single set type as many times as you like as well as dragging and dropping them to adjust their order. You may collapse your sets to conserve space. If you do, a preview of the data contained within it will be displayed. [Third party fieldtypes may control how their data will be previewed](/extending/fieldtypes#replicator-preview). You can prevent certain fields being shown in the preview text by adding `replicator_preview: false`. The following fieldset YAML is an example of what could be used to construct the Replicator shown in the screenshot above: ``` yaml fields: my_replicator_field: type: replicator display: Replicator sets: text: display: Text fields: text: type: markdown image: display: Image fields: photo: type: assets container: main max_files: 1 caption: type: text quote: display: Pull Quote fields: text: type: text cite: type: text pull: type: radio options: left: Left Align right: Right Align ``` ## Set Previews New to Statamic v6, you can add an image preview of your set, _as well as_ an icon. Previews make it easy to identify sets by showing a screenshot of what the rendered set might look like on the front-end. Clients can now say “ah, that one” without pretending to know the names you carefully gave them. ### Configuring Set Previews To add a set preview, click the little "pencil" icon next to the set name.
Replicator Set Edit Preview Replicator Set Edit Preview
Let's give the set a preview.
Once you're in the set editor, you can add a preview image and icon. Here we're showing a lovely screenshot of what the newsletter signup form might look like on the front-end. We can even add some instructions to explain how the set is used.
Replicator Set Previews Replicator Set Previews
Behold a Preview Image, for the love of all clients.
### Set Previews in Action Once you've set a preview image, users adding a replicator set can hover over the set to preview what it might look like on the frontend. You can view previews in two different UI modes: in a list of set names, or in a grid of sets with their preview images.
Replicator Set Previews in the CP Replicator Set Previews in the CP
A preview in a list of sets.
Replicator Set Previews in the CP Replicator Set Previews in the CP
A preview in a grid of sets. Previews fall back to the set icon if no preview image is set.
## Fieldtypes You can use any fieldtypes inside your Replicator sets. Make sure to compare the experience with the other meta-fields: [Grid](/fieldtypes/grid) and [Bard](/fieldtypes/bard). ## Data Structure {#data-structure} Replicator stores your data as an array with the set name as `type`. ```yaml my_replicator_field: - type: text text: "Let's talk about the best new show from 2017!" - type: image photo: /assets/night-manager.jpg caption: The Night Manager - type: quote text: Such fear, such dread, and such a dazzling script. cite: Deborah Ross pull: right ``` :::warning Please note that you **cannot** use a Replicator fieldtype for the `content` field. ::: ## Templating {#templating} Use the tag pair syntax with an `if/else` conditions to style each set accordingly. ::tabs ::tab antlers ```antlers {{ my_replicator_field }} {{ if type == "text" }}
{{ text|markdown }}
{{ elseif type == "quote" }}
{{ text }}

— {{ cite }}

{{ elseif type == "image" }}
{{ caption }}
{{ caption }}
{{ /if }} {{ /my_replicator_field }} ``` ::tab blade ```blade @foreach($my_replicator_field as $set) @if ($set->type === 'text')
{!! Statamic::modify($set->text)->markdown() !!}
@elseif ($set->type === 'quote')
{{ $set->text }}

— {{ $set->cite }}

@elseif ($set->type === 'image')
{{ $set->caption }}
{{ $set->caption }}
@endif @endforeach ``` :: An alternative, and often cleaner, approach is to have multiple 'set' partials and do: ::tabs ::tab antlers ```antlers {{ my_replicator_field }} {{ partial src="sets/{type}" }} {{ /my_replicator_field }} ``` ::tab blade :::tip By using `[...$set]`, you can access the set variables within the set's Blade file without having to reference `$set` for each variable. For example, `{!! $set->text !!}` becomes `{!! $text !!}`. ::: ```blade @foreach ($my_replicator_field as $set) @include('fieldtypes.partials.sets.'.$set->type, [...$set]) @endforeach ``` :: Then inside your partials directory you could have: ::tabs ::tab antlers ``` files theme:serendipity-light resources/views/partials/sets/ text.antlers.html quote.antlers.html image.antlers.html ``` ::tab blade ``` files theme:serendipity-light resources/views/partials/sets/ text.blade.php quote.blade.php image.blade.php ``` :: and the `image` set partial may look something like: ::tabs ::tab antlers ```antlers {{# this is image.antlers.html #}} {{ caption }} ``` ::tab blade ```blade {{-- this is image.blade.php --}}
{{ $caption }}
{{ $caption }}
``` :: ## Custom set icons You can change the icons available in the set picker by configuring an icon set in a service provider. For example, you can drop this into your `AppServiceProvider`'s `boot` method: ```php use Statamic\Fieldtypes\Sets; public function boot() { Sets::useIcons('heroicons', resource_path('svg/heroicons')); } ``` ================================================ FILE: content/collections/fieldtypes/revealer.md ================================================ --- title: Revealer description: A button that reveals conditional fields like magic. intro: The revealer is a simple button that reveals conditional fields without saving boolean button data. id: 54066363-7dec-431c-86c6-7e9353380ef5 screenshot: fieldtypes/screenshots/v6/revealer.gif screenshot_dark: fieldtypes/screenshots/v6/revealer-dark.gif options: - name: display type: string description: The revealer label text. - name: mode type: string *button* description: The revealer input is displayed in `button` mode by default. Choose `toggle` mode if you wish to display a toggle input instead. - name: input_label type: string description: Optionally customize the label on the input itself. - name: instructions type: string description: Instructional text that will appear as a tooltip on the button. --- If you have some fields that you wish to hide until the user is ready to reveal them, throw a Revealer field in there and those fields may be shown once the button is clicked. This fieldtype is intended to be used with our [conditional field rules](/conditional-fields), but unlike regular conditional fields, it will not [disrupt data flow](/conditional-fields#data-flow) on fields hidden by a Revealer.
Conditional fields used with Revealer Conditional fields used with Revealer
An example of field conditions used in conjunction with a Revealer field.
The example image above uses the following field configuration: ``` yaml - handle: behold field: type: revealer display: 'Behold!' - handle: revealed field: type: text display: 'I am revealed!' if: behold: 'equals true' ``` Regardless of whether the button was clicked or not, no boolean data will be saved for the `behold` Revealer button itself. ================================================ FILE: content/collections/fieldtypes/select.md ================================================ --- title: Select description: Choose from predefined options. This field is highly configurable. intro: Give your users a list of options to choose from. This select field is highly configurable with support for search, multiple choice, and creating new options on the fly. screenshot: fieldtypes/screenshots/v6/select.webp screenshot_dark: fieldtypes/screenshots/v6/select-dark.webp options: - name: clearable type: boolean description: | Allow deselecting any chosen option and making null a possible value. Default: `false`. - name: options required: true type: array description: > A set of key/value pairs that define the values and labels. If you don't define the keys, the value and label will be the same. - name: placeholder type: string description: | Set the non-selectable placeholder text. Default: none. - name: default type: string description: | Set the default option key. Default: none. - name: multiple type: boolean description: > Allow multiple selections. Default: `false`. - name: searchable type: boolean description: > Enable search with suggestions by typing in the select box. Default: `true`. - name: taggable type: boolean description: > Use a "tag" style UI when selecting multiples. Default: `false`. - name: push_tags type: boolean description: > Add newly created options to the list. Default: `false`. id: 812bd19d-ec37-42d5-b8f9-310366ef8abe --- ## Overview This field is highly configurable, thanks to the fantastic [Vue Select](https://vue-select.org) component. Be sure to explore all the [config options](#options)! ## Data Storage Select fields will store the _value_ of the chosen option or options. Given this configuration... ``` yaml handle: select field: display: Select options: face: "So's your face." know: "I know you are, but what am I?" hand: "Talk to the hand." beeswax: "Mind your own beeswax." placeholder: 'Choose your snappy comeback' type: select ``` Your saved data will be: ``` yaml select: face ``` ### Options in blueprint YAML {#options-in-blueprint-yaml} In the blueprint file, the field’s `options` list is stored in **expanded** form (an ordered sequence of `key` / `value` pairs) so Statamic can preserve option order everywhere content is stored—including SQL-backed databases. If you author blueprints by hand, use that shape or mirror what the Blueprint Editor writes: ```yaml options: - key: face value: "So's your face." - key: know value: "I know you are, but what am I?" ``` That setting applies to the blueprint definition only, not to the entry value (`select: face` above). ## Templating Select fields return the **value** from your selected option. You can access the label with `select_var:label`. ::tabs ::tab antlers ```antlers

Oh yeah? {{ select:label }}

``` ::tab blade ```blade

Oh yeah? {{ $select['label'] }}

``` :: ```html

Oh yeah? So's your face.

``` ================================================ FILE: content/collections/fieldtypes/sites.md ================================================ --- id: db0162b1-c58c-4093-841c-b386cc2e5c21 blueprint: fieldtype title: Sites screenshot: fieldtypes/screenshots/v6/sites.webp screenshot_dark: fieldtypes/screenshots/v6/sites-dark.webp intro: 'Allows you to select one or more sites when running a [multi site](/multi-site).' options: - name: max_items type: integer description: > The maximum number of items that may be selected. Setting this to `1` will change the UI to a select dropdwon. - name: mode type: string description: | Set the UI style for this field. Can be one of 'default' (Stack Selector), 'select' (Select Dropdown) or 'typeahead' (Typeahead Field). --- ## Usage ```yaml fields: my_sites_field: type: sites ``` The Sites fieldtype is a [Relationship fieldtype](/relationships#fieldtypes), and will save the site or sites as their handles (the config name). ```yaml sites: - english - french - german ``` ## Templating You're more than likely using this field as a way to dynamically fetch content from a specific site other than the current one. The following example assumes `max_items` has been set to `1`: ::tabs ::tab antlers ```antlers
    {{ collection:news :site="my_sites_field" }}
  • {{ title }}
  • {{ /collection:news }}
``` ::tab blade ```blade
  • {{ $title }}
``` :: ```html
  • Bonjour!
  • Ton tonton tond ton thon
  • etc
``` ================================================ FILE: content/collections/fieldtypes/slug.md ================================================ --- id: cbc7ecef-155f-45c0-9ac4-e815e120fa99 blueprint: fieldtype title: Slug screenshot: fieldtypes/screenshots/v6/slug.webp screenshot_dark: fieldtypes/screenshots/v6/slug-dark.webp description: A text input that automatically "slugifies" the value of another field. overview: > A text field that has the ability to automatically "slugify" the value of any other string field to create-your-very-own-lowercase-without-spaces string of your own. This is primarily used to create a entry URL slugs based on the `title` field of that same entry. options: - name: from type: string description: > Target field to automatically create a slug from. **Default:** `title` - name: generate type: boolean description: > Whether to generate the slug automatically. **Default:** `true` --- ================================================ FILE: content/collections/fieldtypes/spacer.md ================================================ --- title: Spacer description: An invisible field to help you structure your blueprints and forms. overview: "The Spacer fieldtype is invisible, perfect for giving your forms some much-needed breathing room. " id: 55043a00-28ee-4977-a10a-6295e903f41b screenshot: fieldtypes/screenshots/v6/spacer.webp screenshot_dark: fieldtypes/screenshots/v6/spacer-dark.webp --- ================================================ FILE: content/collections/fieldtypes/structures.md ================================================ --- title: Structures meta_title: 'Structures Fieldtype' description: 'Create relationships with structures.' intro: | For when you need to create a relationship to one or more [Structures](/structures). This could be useful to pick which version of a sidebar or footer to include on a page, or other similar things. screenshot: fieldtypes/screenshots/v6/structures.webp screenshot_dark: fieldtypes/screenshots/v6/structures-dark.webp options: - name: max_items type: integer description: 'The maximum number of items that may be selected. Setting this to `1` will automatically change the UI to a dropdown.' - name: mode type: string description: Sets the UI mode for choosing your structures. Pick between `Stack Selector`, `Select Dropdown`, or `Typeahead Field`. related_entries: - 3c34ef5c-781e-4a22-a09b-25f58bdb58a8 - ed746608-87f9-448f-bf57-051da132fef7 id: 5a55198f-fcb6-4cb1-aacc-4aec3ad45003 --- ## Overview Use this fieldtype to create a one-way relationship with one or more structures in your site. It's a simple-little-helper type of thing. :::tip [Structures](/structures) come in two flavors: Ordered Collections and Navigations. ::: ## Data Storage The Structures fieldtype stores the `handle` of a single structure as a string, or an array of handles if `max_items` is greater than 1. ``` yaml structures: - main_nav - footer ``` ## Templating Loop through the structures to access their handles and pass them to a [Nav](/tags/nav) tag. ::tabs ::tab antlers ```antlers {{ structures }} {{ /structures }} ``` ::tab blade ```blade @foreach ($structures as $nav) @endforeach ``` :: ```html ``` ================================================ FILE: content/collections/fieldtypes/table.md ================================================ --- title: Table description: Create and manage simple tables of limitless columns and rows. intro: > Creating tables can be a nuisance in a WYSIWYG editor. This fieldtype gives you a way to create flexible tabular data. screenshot: fieldtypes/screenshots/v6/table.gif screenshot_dark: fieldtypes/screenshots/v6/table-dark.gif id: 11e0ab78-7698-44c8-98f1-1194cb12ce28 options: - name: min_rows type: 'integer *0*' description: The minimum number of required rows. - name: max_rows type: integer description: > The maximum number of rows allowed. Once reached the `Add Row` button will disappear. - name: default type: string description: | Set the default value. --- ## Data Structure Data from the Table fieldtype is saved in an array like this: ``` yaml my_table: - cells: - People - Gift - cells: - Kevin - Kerosene - cells: - Buzz - Spider ``` This data format makes it trivial when it comes time to render it templates. ## Templating This fieldtype comes with a handy [`table`](/modifiers/table) modifier, which will turn your data into a simple HTML ``. ::tabs ::tab antlers ```antlers {{ my_table | table }} ``` ::tab blade ```blade {!! Statamic::modify($my_table)->table() !!} ``` :: Here’s the same thing that the modifier would have output, but we’re modifying the cells to use `| markdown`. ::tabs ::tab antlers ```antlers
{{ my_table }} {{ cells }} {{ /cells }} {{ /my_table }}
{{ value | markdown }}
``` ::tab blade ```blade @foreach ($my_table as $row) @foreach ($row['cells'] as $cell) @endforeach @endforeach
{!! Statamic::modify($cell)->markdown() !!}
``` :: Want even more control? This example assumes you have a boolean field in your front-matter named `first_row_headers` which toggles whether or not to render the first row of the table in a `` with `` tags. ::tabs ::tab antlers ```antlers {{ my_table }} {{ if first && first_row_headers }} {{ cells }} {{ /cells }} {{ /if }} {{ if !first && first_row_headers || !first_row_headers }} {{ if first }} {{ /if }} {{ cells }} {{ /cells }} {{ if last }} {{ /if }} {{ /if }} {{ /my_table }}
{{ value|markdown }}
{{ value|markdown }}
``` ::tab blade The following example uses the `fetch` helper function, which resolves `Value` instances for you and returns the underlying value. If the passed value is not a `Value` instance, you will get that original value back. ```blade @php($first_row_headers = fetch($first_row_headers) ?? false) @foreach ($my_table as $row) @if ($loop->first && $first_row_headers) @foreach ($row['cells'] as $value) @endforeach @endif @if (! $loop->first && $first_row_headers || ! $first_row_headers) @if ($loop->first) @endif @foreach ($row['cells'] as $value) @endforeach @if ($loop->last) @endif @endif @endforeach
{!! Statamic::modify($value)->markdown() !!}
{!! Statamic::modify($value)->markdown() !!}
:: ================================================ FILE: content/collections/fieldtypes/taggable.md ================================================ --- title: Tags screenshot: fieldtypes/screenshots/v6/taggable.webp screenshot_dark: fieldtypes/screenshots/v6/taggable-dark.webp description: Enter a list of items with a tag-style interface. overview: > Users can enter “taggable” values, which are formatted automatically into a YAML list format. It's a lot like the [list fieldtype](/fieldtypes/list) but with a different UI. id: 821a636f-2ebd-4297-b459-47e702f899df --- ## Overview Press `enter`, `tab`, or `,` to add a tag. Click an × to remove one. That's all there is to it. ## Data Storage Your tags will get saved as a simple YAML list, like this: ```yaml - applesauce - garbage pants - socks ``` ## Templating Loop through the array items to display each item's `value`. ::tabs ::tab antlers ```antlers

I've heard rumors of:

    {{ tags }}
  • {{ value }}
  • {{ /tags }}
``` ::tab blade ```blade

I've heard rumors of:

    @foreach ($tags as $tag)
  • {{ $tag }}
  • @endforeach
``` :: ```html

I've heard rumors of:

  • applesauce
  • garbage pants
  • socks
``` :::tip This fieldtype uses the word "taggable" in a generic way. If you're looking for a way to tag/categorize your content on a _schema_-level, you should read about [taxonomies](/taxonomies). ::: ================================================ FILE: content/collections/fieldtypes/taxonomies.md ================================================ --- id: 88c9909c-4134-40cd-b095-79ec7207b190 blueprint: fieldtype title: Taxonomies description: 'Choose from one or more taxonomies.' overview: 'Allows you to choose one or more taxonomies.' options: - name: max_items type: integer description: | The maximum number of items that may be selected. Setting this to `1` will change the UI to a select dropdwon. - name: mode type: string description: | Set the UI style for this field. Can be one of 'default' (Stack Selector), 'select' (Select Dropdown) or 'typeahead' (Typeahead Field). screenshot: /fieldtypes/screenshots/taxonomies.png related_entries: - ba832b71-a567-491c-b1a3-3b3fae214703 - 6a18eac8-6139-419c-9d64-a2c960ccc3cd - 42d2d87c-5af6-4856-9ee0-9548439df772 --- ## Usage This fieldtype is used to view and select from a list of Taxonomies. ```yaml fields: my_taxonomies_field: type: taxonomies ``` ## Data Structure The Taxonomies fieldtype is a [Relationship fieldtype](/relationships#fieldtypes), and will save the taxonomies as their handles. ```yaml taxonomies: - genre - cool_factor ``` ## Templating You're more than likely using this field as a way to dynamically display Terms from one or more Taxonomies. The following example assumes `max_items` has been set to `1`: ::tabs ::tab antlers ```antlers
    {{ taxonomy :from="my_taxonomy_field" }}
  • {{ title }}
  • {{ /taxonomy }}
``` ::tab blade ```blade
  • {{ $title }}
``` :: ```html
  • Comedy
  • Drama
  • Dramedy
``` ================================================ FILE: content/collections/fieldtypes/template.md ================================================ --- title: Template description: A template picker with autosuggest. intro: > Used for choosing an entry’s template. Be sure to name the field `template` if you want it to be able to change the template (it's a special variable name). id: 76e0ee52-a3c4-4904-8b5c-f722bbb20482 screenshot: fieldtypes/screenshots/v6/template.webp screenshot_dark: fieldtypes/screenshots/v6/template-dark.webp options: - name: hide_partials type: boolean description: > Since partials are rarely intended to be used as templates, they are hidden by default. --- ## Overview This is generally used as a "system" field to control an entry's template. It points to the `resources/views` directory and will list all available templates therein. ================================================ FILE: content/collections/fieldtypes/terms.md ================================================ --- title: Terms extends: 9dd58c40-6e33-49c8-83fa-61a69f6371be description: Attach Taxonomy Terms to your content. intro: > Allows you attach Taxonomy Terms to your content. They could be Tags, Categories, Colors, Flavors, you name it. We highly recommend [learning more about Taxonomies](/taxonomies) before going any further. screenshot: fieldtypes/screenshots/v6/terms.webp screenshot_dark: fieldtypes/screenshots/v6/terms-dark.webp options: - name: max_items type: integer description: > The maximum number of items that may be selected. Setting this to `1` will change the UI to a dropdown. - name: taxonomy type: string description: > The handle of the Taxonomy from which to fetch Terms. Not needed when placed in the fieldset's `taxonomies` array. In that case, it'll get the taxonomy from the field name. - name: create type: boolean *true* description: > By default you may create new terms. Set to `false` to only allow selecting from existing terms. - name: query_scopes type: string description: > Allows you to specify a [query scope](/extending/query-scopes-and-filters#scopes) which should be applied when retrieving selectable assets. You should specify the query scope's handle, which is usually the name of the class in snake case. For example: `MyAwesomeScope` would be `my_awesome_scope`. id: 31adcc00-4fbb-4fe9-9b48-401061273096 --- ## Overview Taxonomies are usually relationships established on the collection-configuration level. Make sure to read the [Taxonomies documentation](/taxonomies) to understand how everything works. ## Data Structure If the field is being used for taxonomizing your content (ie. the field name matches the taxonomy handle), the term's _slugs_ will be saved. ``` yaml wildlife: - kangaroo - three-toed-sloth - panda - porg ``` However, if you just want to store references to taxonomy terms for other purposes, the term's IDs will be saved. See [below](#without-taxonomizing) for more detail. A term ID is the taxonomy handle combined with the slug. This way, you may reference terms from multiple taxonomies. ``` yaml things_you_may_find_adorable: - wildlife/panda - people/the-elderly ``` ## Templating As outlined in the [Taxonomies Guide](/taxonomies#templating), term slugs will automatically be converted to Term objects which means you will have all of the term's data available as variables. ::tabs ::tab antlers ```antlers ``` ::tab blade ```blade ``` :: ```html ``` ## Using terms without taxonomizing {#without-taxonomizing} The most common use for this fieldtype is to taxonomize, or "tag", your entry. However, sometimes you have other ideas in mind for using taxonomy terms. For instance, you might have a "similar tags" field, or want to create an index of many different, unrelated things. In this case, you aren't tagging the entry itself at all. When using the taxonomy field in this way, terms will get saved using _IDs_ instead of slugs. ``` yaml similar_things: - categories::hats - tags::delightful ``` You can still loop through them like your used to: ``` antlers
    {{ similar_things }}
  • {{ title }}
  • {{ /similar_things }}
``` ```html ``` ================================================ FILE: content/collections/fieldtypes/text.md ================================================ --- title: Text description: A simple text input field for managing short, unformatted text. overview: > A text field that has the ability to morph into an intergalactic dragon and devour entire planets! Just kidding — you just type stuff into the box. Pretty basic. options: - name: append type: string description: > Add text after (to the right of) the text input. - name: character_limit type: integer description: 'Set the maximum number of enterable characters. This is only a recommendation, not a hard limit. To enforce a hard limit, use the [`max`](https://laravel.com/docs/master/validation#rule-max) validation rule.' - name: input_type type: string description: > Control the HTML5 input type. Options: `color`, `date`, `email`, `hidden`, `month`, `number`, `password`, `tel`, `text`, `time`, `url`, and `week`. **Default: `text`.** - name: prepend type: string description: > Add text to the beginning (to the left of) the text input. - name: placeholder type: string description: > Set some default placeholder text. screenshot: fieldtypes/screenshots/v6/text.webp screenshot_dark: fieldtypes/screenshots/v6/text-dark.webp id: 306b112b-b0cc-4359-b681-da353eeb50ac --- ================================================ FILE: content/collections/fieldtypes/textarea.md ================================================ --- title: Textarea description: | A simple textarea field for managing longer, unformatted text. overview: | A long textarea field that functions like a swimming pool for letters and numbers on a hot day. Everyone is welcome and they can stay as long as they want. options: - name: character_limit type: integer description: 'Set the maximum number of enterable characters. This is only a recommendation, not a hard limit. To enforce a hard limit, use the [`max`](https://laravel.com/docs/master/validation#rule-max) validation rule.' - name: placeholder type: string description: > Set some default placeholder text. screenshot: fieldtypes/screenshots/v6/textarea.webp screenshot_dark: fieldtypes/screenshots/v6/textarea-dark.webp id: 7c54484a-7ba5-4314-b9af-9d9a462090fc --- ================================================ FILE: content/collections/fieldtypes/time.md ================================================ --- title: Time description: A timepicker. It lets you pick a time. intro: The original time field from the set of Kiefer Sutherland's hit drama "24". It's a simple timepicker that operates in 24-hour mode and supports keyboard `up` and `down` controls. screenshot: fieldtypes/screenshots/v6/time.webp screenshot_dark: fieldtypes/screenshots/v6/time-dark.webp id: ccfbaf71-7823-4f71-a375-e874035f80ca --- None. It just works. ================================================ FILE: content/collections/fieldtypes/toggle.md ================================================ --- title: Toggle description: A toggle switch for booleans (`true` and `false`). intro: A nice little toggle switch generally used to manage settings-type variables. It stores `true` or `false` and is delightfully uncomplicated, just like our relationship with yogurt. screenshot: fieldtypes/screenshots/v6/toggle.webp screenshot_dark: fieldtypes/screenshots/v6/toggle-dark.webp id: ac5f8f98-616f-4621-a7ee-dbc8bbc15525 --- ## Can I haz green? Some people like their toggles green. It's a personal preference, just like white or milk chocolate. Since the control panel theme is customizable, you can make your toggles green! Or whatever other color for that matter… but maybe not red? Look for the Theme section in `config/statamic/cp.php` and set the `switch-bg` your preferred color. Here's green: ```php 'theme' => [ 'switch-bg' => Color::Green[500], 'dark-switch-bg' => Color::Green[600], ], ```
Statamic Toggle Green Statamic Toggle Green
Who dares to dream? A nugget of purest green!
## Data Structure Flicking the toggle to the right sets to the value to `true`, left to `false`. ``` yaml do_the_thing: true ``` ## Templating Toggles are usually used to control logic, so you can combine them with `{{ if }}` statements in your templates to handle all manner of show/hide wizardry. ::tabs ::tab antlers ```antlers {{ if do_the_thing }} It does it {{ /if }} ``` ::tab blade The following example uses the `fetch` helper function, which resolves `Value` instances for you and returns the underlying value. This way you always get the real "truthy" value, regardless of how you retrieved `$do_the_thing`. ```blade @if (fetch($do_the_thing)) It does it @endif ``` :: ================================================ FILE: content/collections/fieldtypes/user-groups.md ================================================ --- id: 006ee3c1-607e-4d65-94ae-6862c18ac516 title: User Groups screenshot: fieldtypes/screenshots/v6/user-groups.webp screenshot_dark: fieldtypes/screenshots/v6/user-groups-dark.webp description: Create a relationship with a User Group overview: > Use this fieldtype to create a relationship with [User Groups](/users#user-groups). pro: true options: - name: max_items type: integer required: false description: 'The maximum number of user groups that may be selected.' - name: mode type: string description: | Set the UI style for this field. Can be one of `default` (Stack Selector), `select` (Select Dropdown) or `typeahead` (Typeahead Field). related_entries: - 57184c18-28d3-433f-b6ee-0e4539f6b504 - 6b691e04-8f28-4eb2-8288-b61433883fe4 --- ## Overview The User Group fieldtype gives your users a way to pick one or more User Groups to attach to the current entry. What you do with that relationship is up to you, but most likely you'll be either listing users or combining it with the [User:In](/tags/user-in) tag to protect content or areas of the frontend. ## Data Storage The User Group fieldtype stores the `handle` of a single group as a string, or an array of handles if `max_items` is greater than 1. ## Templating The User Group fieldtype uses [augmentation](/augmentation) to return the `title` and `handle` of each Group. You can use pass these values into the `{{ user:in }}` tag to protect content. The following example assumes `max_items` has been set to `1`. ::tabs ::tab antlers ```antlers {{ user:in :group="group_field:handle" }} You are in the {{ group_field:title }} group. Nice! {{ /user:in }} ``` ::tab blade ```blade {{-- Using Statamic Tags --}} You are in the {{ $group_field->title }} group. Nice! {{-- Using Fluent Tags --}} @if(Statamic::tag('user:in')->group($group_field->handle)->fetch()) You are in the {{ $group_field->title }} group. Nice! @endif ``` :: ================================================ FILE: content/collections/fieldtypes/user-roles.md ================================================ --- id: 42baf054-f3d5-4317-b7dd-466882c47c06 blueprint: fieldtype title: 'User Roles' screenshot: fieldtypes/screenshots/v6/user-roles.webp screenshot_dark: fieldtypes/screenshots/v6/user-roles-dark.webp description: 'Create a relationship with a User Role' overview: | Use this fieldtype to create a relationship with [User Roles](/users#user-roles). pro: true options: - name: max_items type: integer required: false description: 'The maximum number of user roles that may be selected.' - name: mode type: string description: | Set the UI style for this field. Can be one of `default` (Stack Selector), `select` (Select Dropdown) or `typeahead` (Typeahead Field). related_entries: - 6b691e04-8f28-4eb2-8288-b61433883fe4 - 8c7f38bb-ee6f-43ee-b775-4eeae0a87bf3 --- ## Overview The User Role fieldtype gives your users a way to pick one or more User Roles to attach to the current entry. What you do with that relationship is up to you, but most likely you'll be either listing users or combining it with the [User:Is](/tags/user-is) tag to protect content or areas of the frontend. ## Data Storage The User Role fieldtype stores the `handle` of a single group as a string, or an array of handles if `max_items` is greater than 1. ## Templating The User Role fieldtype uses [augmentation](/augmentation) to return the `title` and `handle` of each Role. You can use pass these values into the `{{ user:is }}` tag to protect content. The following example assumes `max_items` has been set to `1`. ::tabs ::tab antlers ```antlers {{ user:is :role="role_field:handle" }} You are a {{ role_field:title }}. Nice! {{ /user:is }} ``` ::tab blade ```blade {{-- Using Statamic Tags --}} You are a {{ $role_field->title }}. Nice! {{-- Using Fluent Tags --}} @if(Statamic::tag('user:is')->role($role_field->handle)->fetch()) You are a {{ $role_field->title }}. Nice! @endif ``` :: ================================================ FILE: content/collections/fieldtypes/users.md ================================================ --- title: Users description: Relate users with your content. intro: > Attach users to your content to show authorship, list team members, assign the winners of a foot race, or even winners of an elbow race. screenshot: fieldtypes/screenshots/v6/users.webp screenshot_dark: fieldtypes/screenshots/v6/users-dark.webp options: - name: default type: string description: > Setting to `current` will default the field to the currently logged in user. - name: max_items type: integer description: > The maximum number of users than can be selected. Leave it empty for no limit (default). Setting to `1` will save the value as a `string` instead of an `array` and will switch to a select dropdown UI. - name: mode type: string description: > Choose between `select`, `typeahead`, and the `default` stack selector UI modes. - name: query_scopes type: string description: > Allows you to specify a [query scope](/extending/query-scopes-and-filters#scopes) which should be applied when retrieving selectable assets. You should specify the query scope's handle, which is usually the name of the class in snake case. For example: `MyAwesomeScope` would be `my_awesome_scope`. id: 0f8102b9-c948-4264-8cb8-cbfbd0415a04 --- ## Overview The most common use for the Users fieldtype is to set the "author" for entries, but it's not the only use. You could... - List people who contributed to a project - Link to related authors - Manage an "Employee of the Weekend" section. Everyone wants to be the King or Queen of Inventory Saturday, right? - Display team bios - Pull in customer's testimonials through their user account. ## Data Structure The Users fieldtype is a [relationship fieldtype](/extending/relationship-fieldtypes) – which mean the data will store a reference to the users IDs to main a dynamic link. ```yaml author: abc-123-cba-321 ``` ## Templating All relationship fields use [augmentation](/augmentation) to fetch the actual data objects, allowing you to interact with the related data automatically and dynamically. The following example assumes `max_items` has been set to `1`. :::hint When `max_items: 1`, the field augments directly to the related user so you can use dot/colon notation (`{{ author:name }}`). If you'd rather always get a query builder back — so you can chain scopes or filters — enable [`always_augment_to_query`](/augmentation#always-augment-relationships-to-a-query). ::: ::tabs ::tab antlers ```antlers
{{ author }} Avatar of {{ name }}

{{ name }}

{{ email }}

{{ /author }}
``` ::tab blade ```blade
Avatar of {{ $author->name }}

{{ $author->name }}

{{ $author->email }}

``` :: ```html
Avatar of David Hasselhoff

David Hasselhoff

thehoff@statamic.com

``` ================================================ FILE: content/collections/fieldtypes/video.md ================================================ --- title: Video description: Extract embed URLs from Youtube, Vimeo, and HTML5 compatible video links and preview them right inline. intro: | Extract embed URLs from Youtube, Vimeo, and HTML5 compatible video links and preview them right inline. Feel free watch the whole thing instead of working – we won't tell. screenshot: fieldtypes/screenshots/v6/video.webp screenshot_dark: fieldtypes/screenshots/v6/video-dark.webp id: ced8b901-95bd-4006-b70e-4ea04d72fcb7 --- ## Usage Enter a video URL and it will be loaded in an embedded player directly beneath the field so you can preview it. You may enter: - YouTube URLs: `https://www.youtube.com/watch?v=s9F5fhJQo34` - Vimeo URLs: `https://vimeo.com/22439234` - mp4, ogv, mov, or webm URLs: `http://example.com/video.mp4` ## Data Structure The Video field will save the URL of the video you've entered. If you paste embed code into the field, it will extract the proper URL for you. ``` yaml video: https://www.youtube.com/watch?v=s9F5fhJQo34 ``` ## Templating You can use the [is_embeddable](/modifiers/is_embeddable) and [embed_url](/modifiers/embed_url) modifiers to display your video player. ::tabs ::tab antlers ```antlers {{ if video | is_embeddable }} {{ else }} {{ /if }} ``` ::tab blade ```blade @if (Statamic::modify($video)->isEmbeddable()->fetch()) @else @endif ``` :: ================================================ FILE: content/collections/fieldtypes/width.md ================================================ --- id: b8b51bb8-a4bd-4aec-90bd-4f150a29c8a0 blueprint: fieldtype title: Width screenshot: fieldtypes/screenshots/v6/width.webp screenshot_dark: fieldtypes/screenshots/v6/width-dark.webp intro: 'A slick way to select a width in your blueprints. Although you could use it for anything you want as it stores the value in your markdown as an integer. Neat!' width_field: 50 options: - name: options type: array required: false description: 'The array of integers presented in the blueprint. Default: 25 / 33 / 50 / 66 / 75 / 100' - name: default type: integer required: false description: "The default selected width. Can be set to a value that doesn't exist in the options array if desired." --- ## Overview An alternative to the [Button Group](/fieldtypes/button_group) or [Select](/fieldtypes/select) field types that is a bit more compact and visually appealing. ## Data structure The selected value is stored as an integer. ``` yaml image_width: 50 ``` ## Templating Use in your front end, most likely with a CSS framework like Tailwind or Bootstrap to set custom widths. ::tabs ::tab antlers ```antlers

Compact headings

With wider sub-headings

``` ::tab blade ```blade

Icon overview

``` :: ================================================ FILE: content/collections/fieldtypes/yaml.md ================================================ --- title: YAML description: A YAML editor that _directly_ manages YAML. overview: > A [code fieldtype](/fieldtypes/yaml) in YAML mode that _directly_ edits and stores YAML instead of an escaped string representation of said YAML. screenshot: fieldtypes/screenshots/v6/yaml.webp screenshot_dark: fieldtypes/screenshots/v6/yaml-dark.webp id: 25155800-8fd7-46c7-aad0-5daaf07543da --- ## Overview This field is a [code fieldtype](/fieldtypes/code) that gets saved as YAML instead of a string. Your input is validated on save to make sure you don't write _invalid_ YAML. :::tip The YAML field is one of the "catch-all" solutions for when there's no better way to work with an odd data structure. **Recommended for developers only.** ::: ## Data Storage You really should [know YAML](/yaml) if you're using this field, in which case you'll understand how the data is stored – exactly as written. ## Templating Refer to the [YAML guide](/yaml) on how to work with data in general. We really can't be any more specific here. You understand, right? ================================================ FILE: content/collections/fieldtypes.yaml ================================================ title: Fieldtypes icon: fieldsets template: page layout: layout mount: 25f01cb5-1ca9-44a1-af1b-885b32b05cc8 revisions: false route: '/fieldtypes/{slug}' sort_dir: asc date_behavior: past: null future: null preview_targets: - label: Entry url: '{permalink}' refresh: true inject: view_model: App\ViewModels\Fieldtypes ================================================ FILE: content/collections/modifiers/add.md ================================================ --- id: 53debd55-5d53-4254-ad86-49a26cb09594 blueprint: modifiers modifier_types: - math title: Add --- Add a value or another variable to your variable. Pass an integer or the name of a second variable as the parameter. Also supports `+` as shorthand. ``` yaml books: 5 magazines: 10 ``` ::tabs ::tab antlers ```antlers {{ books | add:5 }} {{ books | add:magazines }} {{ books | +:magazines }} ``` ::tab blade ```blade {{-- Using Modifiers --}} {{ Statamic::modify($books)->add(5) }} {{ Statamic::modify($books)->add($magazines) }} {{ Statamic::modify($books)->add($magazines) }} {{-- Using PHP --}} {{ $books + 5 }} {{ $books + $magazines }} {{ $books + $magazines }} ``` :: ```text 10 15 15 ``` ================================================ FILE: content/collections/modifiers/add_slashes.md ================================================ --- id: c5832187-290e-4701-aa74-316d8130e7bb modifier_types: - string title: 'Add Slashes' --- Modifies a string by adding backslashes before characters that need to be escaped. These characters are: - single quote `'` - double quote `"` - backslash `\` This is most often used when passing string data into JavaScript. ``` yaml summary: > "I'm not listening!" said the small, strange creature. ``` ::tabs ::tab antlers ```antlers {{ summary | add_slashes }} ``` ::tab blade ```blade {{ Statamic::modify($summary)->addSlashes() }} ``` :: ``` output \"I\'m not listening!\" said the small, strange creature. ``` ================================================ FILE: content/collections/modifiers/ampersand_list.md ================================================ --- id: cbab1bb5-302e-499d-badb-f154dbae751d blueprint: modifiers modifier_types: - array - markup title: 'Ampersand List' related_entries: - d8a8568c-bb93-4e84-8d30-e527b3b02876 - 6866c25b-1266-4908-8325-dce4e5146f5b - eed4c5bc-0923-4f54-ad37-ca9a3384e1e0 - 9dfc5020-3d14-4774-a1f6-d82d051cb964 --- Turn a simple array into a comma delimited string with a friendly little ampersand between the last two items. ```yaml fruits: - apples - bananas - jerky ``` ::tabs ::tab antlers ```antlers {{ fruits | ampersand_list }} ``` ::tab blade ```blade {{ Statamic::modify($fruits)->ampersandList() }} ``` :: ```html apples, bananas & jerky ``` ================================================ FILE: content/collections/modifiers/antlers.md ================================================ --- id: e3b58c77-0bfc-40da-918f-51a7f65950b8 blueprint: modifiers modifier_types: - string - utility title: Antlers --- Parses the given value as an Antlers template. ```yaml title: 'Hello {{ audience }}!' audience: world ``` ::tabs ::tab antlers ```antlers {{ title | antlers }} ``` ::tab blade ```blade {{ Statamic::modify($title)->antlers() }} ``` :: ``` Hello world! ``` ================================================ FILE: content/collections/modifiers/as.md ================================================ --- id: ada24ec2-1b6e-4759-b2c0-06d9d464f3f9 blueprint: modifiers modifier_types: - array - utility title: As --- Alias an array variable as another name, allowing you to massage your data to reused shared components and templates. ```yaml blocks: - type: text content: I love to eat tacos in the bathroom. - type: photo photo: /assets/img/baño-tacos.jpg ``` ```antlers {{ blocks as="sets" }} {{ sets }} {{ partial:type }} {{ /sets }} {{ /blocks }} ``` ```html

I like to eat tacos in the bathroom.

``` ================================================ FILE: content/collections/modifiers/ascii.md ================================================ --- id: 807d2a16-dbaa-4aaf-887c-9682b63a6af8 blueprint: modifiers modifier_types: - string title: Ascii --- Replaces all non-ASCII characters with their closest ASCII counterparts and removes any unsupported characters completely. This is very useful for converting foreign language strings into something more code-friendly. ```yaml title: lemoñade ``` ::tabs ::tab antlers ```antlers {{ title | ascii }} ``` ::tab blade ```blade {{ Statamic::modify($title)->ascii() }} ``` :: ```html lemonade ``` ================================================ FILE: content/collections/modifiers/at.md ================================================ --- id: 40fb38b6-a2b0-411d-b90b-543b38ac8aa3 blueprint: modifiers modifier_types: - string title: At --- Returns the single character at a given position in a string. It starts at zero with the first character. ```yaml title: supercalifragilisticexpialidocious ``` ::tabs ::tab antlers ```antlers {{ title | at:21 }} ``` ::tab blade ```blade {{ Statamic::modify($title)->at(21) }} ``` :: ```html x ``` ================================================ FILE: content/collections/modifiers/attribute.md ================================================ --- id: 9840b1ab-4576-4cc9-9b83-9226d069807d blueprint: modifiers modifier_types: - string - array title: Attribute --- When you're writing partials, you might find yourself passing variables that will ultimately just be used in HTML attributes, like this: ```antlers ``` You can simplify this using the `attribute` modifier: ```yaml name: first_name class: text-sm font-mono text-gray-900 mandatory: true ``` ```antlers ``` ```html ``` The `attribute` modifier supports passing booleans, *some* objects, arrays, integers, floats and strings. ================================================ FILE: content/collections/modifiers/background_position.md ================================================ --- id: 0904f610-eee8-4b86-827b-0dc281d553ca blueprint: modifiers modifier_types: - asset - string title: 'Background Position' --- Converts an asset focal point value (eg. `50-30`) into a value suitable for the background-position css property. ```yaml focus: 50-30 ``` ::tabs ::tab antlers ```antlers background-position: {{ focus | background_position }}; ``` ::tab blade ```blade background-position: {{ Statamic::modify($focus)->backgroundPosition() }}; ``` :: ```html background-position: 50% 30%; ``` ================================================ FILE: content/collections/modifiers/backspace.md ================================================ --- id: 9526d59a-3abe-444f-b9a4-b0a8ed2fa880 blueprint: modifiers modifier_types: - string title: Backspace --- Removes a specified number of characters from the end of a string. ```yaml title: supercalifragilisticexpialidocious ``` ::tabs ::tab antlers ```antlers {{ title | backspace:29 }} ``` ::tab blade ```blade {{ Statamic::modify($title)->backspace(29) }} ``` :: ```html super ``` ================================================ FILE: content/collections/modifiers/bard_html.md ================================================ --- id: e2b731c3-13aa-42c8-96f1-9b999a0121e0 blueprint: modifiers title: 'Bard HTML' modifier_types: - array - string - utility --- Converts any Bard data to an HTML string (excluding sets). Bard data can be either: * The raw value from a Bard field (a ProseMirror document), with or without sets * One or more ProseMirror nodes (from the [bard_items](bard_items) modifier) ```yaml main_content: - type: paragraph content: - type: text text: "We're going to build a simple personal website for a fictitious young aspiring programmer named Kurt Logan." - type: set attrs: values: type: code_block code: '' - type: paragraph content: - type: text text: "Kurt always has and always will live in the 1980s and is very excited at the prospect of having his very own place in\_CYBERSPACE." ``` ::tabs ::tab antlers ```antlers {{ main_content | raw | bard_html }} ``` ::tab blade ```blade {{ Statamic::modify($main_content)->bardHtml() }} ``` :: ```html

We're going to build a simple personal website for a fictitious young aspiring programmer named Kurt Logan.

Kurt always has and always will live in the 1980s and is very excited at the prospect of having his very own place in CYBERSPACE.

``` ================================================ FILE: content/collections/modifiers/bard_items.md ================================================ --- id: 3c1a5985-1157-4287-801f-95a44b158c82 blueprint: modifiers title: 'Bard Items' modifier_types: - array - utility --- Converts any Bard data to a flat array of ProseMirror nodes and marks. Bard data can be either: * The raw value from a Bard field (a ProseMirror document), with or without sets * One or more of the ProseMirror nodes returned from this modifier ```yaml main_content: - type: paragraph content: - type: text text: "We're going to build a " - type: text marks: - type: link attrs: href: 'http://localhost/' text: 'simple personal' - type: text text: ' website for a fictitious young aspiring programmer named Kurt Logan' - type: paragraph content: - type: image attrs: src: 'asset::assets::donut.jpg' - type: text text: "Kurt always has and always will live in the 1980s and is very excited at the prospect of having his very own place in\_CYBERSPACE." ``` ::tabs ::tab antlers ```antlers {{ main_content | raw | bard_items }} {{ main_content | raw | bard_items | where:type:image | first | bard_html }} {{ links = main_content | raw | bard_items | where:type:link }} {{ links }} {{ node | bard_text }} - {{ attrs:href }} {{ /links }} ``` ::tab blade ```blade bardItems(); Statamic::modify($bard_field_with_sets)->bardItems()->where('type:text')->first()->bardHtml(); $links = Statamic::modify($bard_field_with_sets)->bardItems()->where('type:link')->fetch(); ?> @foreach ($links as $link) {{ Statamic::modify($link['node'])->bardText() }} - {{ $link['attrs']['href'] }} @endforeach ``` :: ```yaml value: - type: paragraph content: - type: text text: "We're going to build a " - type: text marks: - type: link attrs: href: 'http://localhost/' text: simple personal - type: text text: ' website for a fictitious young aspiring programmer named Kurt Logan' - type: text text: "We're going to build a " - type: text marks: - type: link attrs: href: 'http://localhost/' text: simple personal - type: link attrs: href: 'http://localhost/' node: type: text marks: - type: link attrs: href: 'http://localhost/' text: simple personal - type: text text: ' website for a fictitious young aspiring programmer named Kurt Logan' - type: paragraph content: - type: image attrs: src: 'asset::assets::donut.jpg' - type: text text: "Kurt always has and always will live in the 1980s and is very excited at the prospect of having his very own place in\_CYBERSPACE." - type: image attrs: src: 'asset::assets::donut.jpg' - type: text text: "Kurt always has and always will live in the 1980s and is very excited at the prospect of having his very own place in\_CYBERSPACE." ``` ```html ``` ``` simple personal - http://localhost/ ``` ================================================ FILE: content/collections/modifiers/bard_text.md ================================================ --- id: 5a617e74-0878-4b29-bd39-f1e2496d01cd blueprint: modifiers title: 'Bard Text' modifier_types: - array - string - utility --- Converts any Bard data to a plain text string (excluding sets). Bard data can be either: * The raw value from a Bard field (a ProseMirror document), with or without sets * One or more ProseMirror nodes (from the [bard_items](bard_items) modifier) ```yaml main_content: - type: paragraph content: - type: text text: "We're going to build a simple personal website for a fictitious young aspiring programmer named Kurt Logan." - type: set attrs: values: type: code_block code: '' - type: paragraph content: - type: text text: "Kurt always has and always will live in the 1980s and is very excited at the prospect of having his very own place in\_CYBERSPACE." ``` ::tabs ::tab antlers ```antlers {{ main_content | raw | bard_text }} {{ main_content | raw | bard_text | read_time }} ``` ::tab blade ```blade {{ Statamic::modify($main_content)->bardText() }} {{ Statamic::modify($main_content)->bardText()->readTime() }} ``` :: ``` We're going to build a simple personal website for a fictitious young aspiring programmer named Kurt Logan. Kurt always has and always will live in the 1980s and is very excited at the prospect of having his very own place in CYBERSPACE. ``` ``` 1 ``` ================================================ FILE: content/collections/modifiers/bool_string.md ================================================ --- id: c3214196-3d0d-4a3d-b6c3-1ee4960cfedd blueprint: modifiers modifier_types: - utility title: 'Bool String' --- Converts a truthy value to the string `true` and a falsy to the string `false`. Check out [https://www.php.net/manual/en/language.types.boolean.php](https://www.php.net/manual/en/language.types.boolean.php) to see what PHP considers truthy and falsy. ```yaml no: 0 yes: "hell, yea" sure: -1 ``` ::tabs ::tab antlers ```antlers {{ no | bool_string }} {{ yes | bool_string }} {{ sure | bool_string }} ``` ::tab blade ```blade {{ Statamic::modify($no)->boolString() }} {{ Statamic::modify($yes)->boolString() }} {{ Statamic::modify($sure)->boolString() }} ``` :: ```html false true true ``` ================================================ FILE: content/collections/modifiers/camelize.md ================================================ --- id: fe6dbf39-7870-4aa4-9acb-23b4cbf4bf87 blueprint: modifiers modifier_types: - string title: Camelize --- Returns a camelCase version of a string. Trims surrounding spaces, capitalizes letters following digits, spaces, dashes and underscores, and removes spaces, dashes and underscores. It's a programmer-type thing, great for converting between code styles. ```yaml method: make_everything_better ``` ::tabs ::tab antlers ```antlers {{ method | camelize }} ``` ::tab blade ```blade {{ Statamic::modify($method)->camelize() }} ``` :: ```html makeEverythingBetter ``` ================================================ FILE: content/collections/modifiers/cdata.md ================================================ --- id: f1b59bce-43e7-41a4-b82f-e16016d90b18 blueprint: modifiers modifier_types: - string - utility title: CDATA --- Wraps a string in [CDATA][cdata] tags, useful for formatting characters properly in XML. ```yaml title: My Very Own Podcast ``` ::tabs ::tab antlers ```antlers {{ title | cdata }} ``` ::tab blade ```blade {!! Statamic::modify($title)->cdata() !!} ``` :: ```html ``` [cdata]: https://en.wikipedia.org/wiki/CDATA ================================================ FILE: content/collections/modifiers/ceil.md ================================================ --- id: 29863a25-6283-4338-baf5-82bd7c57541c blueprint: modifiers modifier_types: - math - utility title: Ceil --- Rounds a number up to the next whole number. ```yaml number: 25.98 ``` ::tabs ::tab antlers ```antlers {{ number | ceil }} ``` ::tab blade ```blade {{ Statamic::modify($number)->ceil() }} ``` :: ```html 26 ``` ================================================ FILE: content/collections/modifiers/chunk.md ================================================ --- id: 10b38f50-e33c-47e0-8e94-bc4dc551600f blueprint: modifiers modifier_types: - array - markup - utility title: Chunk --- Break arrays or collections into smaller (wait for it) chunks of any given size. This is useful for performing various gymnastics with your HTML markup. :::tip Want a set number of _groups_ regardless of item count? The [split](/modifiers/split) modifier does the opposite — give it a _count_ and it divides the collection into that many roughly equal pieces. ::: ::tabs ::tab antlers ```antlers {{ collection:news as="posts" limit="6" }} {{ posts chunk="3" }}
{{ chunk }} {{ title }} {{ /chunk }}
{{ /posts }} {{ /collection:news }} ``` ::tab blade ```blade @foreach (Statamic::modify($posts)->chunk(3) as $chunk)
@foreach ($chunk['chunk'] as $entry) {{ $entry->title }} @endforeach
@endforeach
``` :: ```html ``` ================================================ FILE: content/collections/modifiers/classes.md ================================================ --- id: ef8d4f96-e811-4343-a2cf-81e455ee8227 blueprint: modifiers modifier_types: - string - array title: Classes --- This conditionally compiles a CSS class string using Laravel's `Arr::toCssClasses()` method. The modifier expects an array of classes where the array key contains the class or classes you wish to add, while the value is a boolean expression. ```yaml is_active: false has_error: true ``` ::tabs ::tab antlers ```antlers
//
``` ::tab blade ```blade true, 'font-bold' => $is_active, 'bg-red' => $has_error ])->classes(); ?>
//
``` You can also use Blade's `@class` directive: ```blade
$is_active, 'bg-red' => $has_error ]) > //
``` :: ```html
//
``` ================================================ FILE: content/collections/modifiers/collapse.md ================================================ --- id: ea17da24-79b9-4ac7-84ba-660b29f95899 blueprint: modifiers modifier_types: - array - utility title: Collapse --- Collapses an array of arrays into a flat array. If duplicate keys exist they *will* get stomped over. ```yaml numbers: - [one, two, three] - [four, five, six] ``` ::tabs ::tab antlers ```antlers {{ numbers | collapse }} ``` ::tab blade ```blade collapse()->fetch(); ?> ``` :: ```yaml numbers: - one - two - three - four - five - six ``` ================================================ FILE: content/collections/modifiers/collapse_whitespace.md ================================================ --- id: bfc66a6c-e4f5-462b-822e-04c3402b5b8f blueprint: modifiers modifier_types: - string title: 'Collapse Whitespace' --- Trims a string and replaces consecutive whitespace characters with a single space. This includes tabs and newline characters, as well as multibyte whitespace such as the thin space and ideographic space. ```yaml title: Bad at typing ``` ::tabs ::tab antlers ```antlers {{ title | collapse_whitespace }} ``` ::tab blade ```blade {{ Statamic::modify($title)->collapseWhitespace() }} ``` :: ```html Bad at typing ``` ================================================ FILE: content/collections/modifiers/compact.md ================================================ --- id: a29dcde5-5708-4ed3-8d00-76f818095477 blueprint: modifiers modifier_types: - array - utility title: Compact --- Converts a comma-delimited list of variable names into an array that can be used anywhere. Arrays are accepted. It allows colon delimited syntax to target nested variables. ```yaml title: 'The finest title there ever was' stuff: one: 'Value One' two: 'Value Two' ``` ::tabs ::tab antlers ```antlers {{ "stuff:one, title, stuff:two" | compact | ul }} ``` ::tab blade ```blade {!! Statamic::modify("stuff:one, title, stuff:two")->compact()->ul() !!} ``` :: Would produce the following output: ::tabs ```html
  • Value One
  • The finest title there ever was
  • Value Two
``` :::tip It's similar to PHP's `compact()` function. ```php $foo = 'bar'; $baz = 'qux'; compact('foo', 'baz'); // ['bar', 'qux'] ``` ::: ================================================ FILE: content/collections/modifiers/console_log.md ================================================ --- id: 985dc29c-fe71-464e-bb83-4f3f2aa455c0 blueprint: modifiers modifier_types: - utility title: 'Console Log' --- Debug a variable by dumping its contents to your browser's JavaScript's console via `console.log`. ```yaml fruit: - apples - bananas - bacon ``` ::tabs ::tab antlers ```antlers {{ fruit | console_log }} ``` ::tab blade ```blade @php(Statamic::modify($fruit)->consoleLog()) ``` :: ```js ["apples", "banana", "jerky"] ``` ================================================ FILE: content/collections/modifiers/contains.md ================================================ --- id: 75145be0-966f-490e-af3d-ed122eb6445b blueprint: modifiers modifier_types: - conditions - array - string title: Contains --- Check if a value contains another value. Supports both strings and arrays. Returns `true` if a match is found, otherwise `false`. The first parameter is the "needle" to find in the "haystack". It will read from the context if there is a matching variable, otherwise it will use the parameter as the value. ## Strings Case-insensitive by default but can be made sensitive by setting the second parameter to `true`. ```yaml summary: "It was the best of times, it was the worst of times." adjective: best noun: carrot ``` ::tabs ::tab antlers ```antlers {{ if summary | contains('BEST') }} {{ if summary | contains('BEST', true) }} {{ if summary | contains('adjective') }} {{ if summary | contains('noun') }} ``` ::tab blade ```blade @if (Statamic::modify($summary)->contains('BEST')->fetch()) ... @endif @if (Statamic::modify($summary)->contains(['BEST', true])->fetch()) ... @endif @if (Statamic::modify($summary)->contains('adjective')->fetch()) ... @endif @if (Statamic::modify($summary)->contains('noun')->fetch()) ... @endif ``` :: ```html true (the substring "BEST" was in the string, and it didn't care about the case.) false (the substring "BEST" was in the string, however it didn't match the case.) true (there's a field named "adjective", and it got the value which was "best") false (there's a field named "noun", and it got the value which was "carrot") ``` ## Arrays You can set strict type checking by setting the second parameter to `true`. ```yaml foods: - bacon - bread - tomato delicious: bacon gross: broccoli numbers: [1, 2] number: '1' ``` ::tabs ::tab antlers ```antlers {{ if foods | contains('bacon') }} {{ if foods | contains('delicious') }} {{ if foods | contains('gross') }} {{ if (foods | contains('vegan bacon strips')) }} {{ if numbers | contains(number) }} {{ if numbers | contains(number, true) }} ``` ::tab blade ```blade @if (Statamic::modify($foods)->contains('bacon')->fetch()) ... @endif @if (Statamic::modify($foods)->contains('delicious')->fetch()) ... @endif @if (Statamic::modify($foods)->contains('gross')->fetch()) ... @endif @if (Statamic::modify($foods)->contains('vegan bacon strips')->fetch()) ... @endif @if (Statamic::modify($foods)->contains($number)->fetch()) ... @endif @if (Statamic::modify($foods)->contains([$number, true])->fetch()) ... @endif ``` :: ```html true (there's no field named "bacon", so it searched for literally "bacon") true (there's a field named "delicious", and it got the value which was "bacon") false (there's a field named "gross", and it got the value which was "broccoli") true (there's no field named "vegan bacon strips", so it searched the expression for a literal string "vegan bacon strips") true (the value of "number" is the string "1", which is fine in non-strict mode) false (with strict mode enabled, the string "1" won't match the integer) ``` ================================================ FILE: content/collections/modifiers/contains_all.md ================================================ --- id: e08b1034-5d58-4fae-8f6a-8efd9a65d6d9 blueprint: modifiers modifier_types: - conditions title: 'Contains All' --- Search a string against multiple needles and return `true` if all are found, otherwise `false`. Case-insensitive. ```yaml summary: "It was the best of times, it was the worst of times." ``` ::tabs ::tab antlers ```antlers {{ if summary | contains_all('best', 'worst') }} {{ if summary | contains_all('best', 'better') }} ``` ::tab blade ```blade @if (Statamic::modify($summary)->containsAll(['best', 'worst'])->fetch()) ... @endif @if (Statamic::modify($summary)->containsAll(['best', 'better'])->fetch()) ... @endif ``` :: ```html true false ``` ================================================ FILE: content/collections/modifiers/contains_any.md ================================================ --- id: 20ac3e9a-4a45-4c2f-9052-be222fc84016 blueprint: modifiers modifier_types: - conditions title: 'Contains Any' --- Search a string against multiple needles and return `true` if any are found, otherwise `false`. Case-insensitive. ```yaml summary: "It was the best of times, it was the worst of times." ``` ::tabs ::tab antlers ```antlers {{ if summary | contains_any('good', 'better', 'best') }} ``` ::tab blade ```blade @if (Statamic::modify($summary)->containsAny(['good', 'better', 'best'])->fetch()) @endif ``` :: ```html true ``` ================================================ FILE: content/collections/modifiers/count.md ================================================ --- id: a7b58312-3498-4807-b2bc-6fcb640fe231 blueprint: modifiers modifier_types: - array title: Count --- Count the number of items in an array. ```yaml fruit: - apples - bananas - bacon ``` ::tabs ::tab antlers ```antlers {{ fruit | count }} ``` ::tab blade ```blade {{ Statamic::modify($fruit)->count() }} ``` :: ```html 3 ``` ================================================ FILE: content/collections/modifiers/count_substring.md ================================================ --- id: 84c6f375-10ca-4296-89a8-a22b9652b5d5 blueprint: modifiers modifier_types: - string title: 'Count Substring' --- Returns the number of occurrences of search term in a given string. By default, the comparison is case-insensitive, but can be made sensitive by setting the second parameter to `true`. ```yaml quote: | Dude! You got a tattoo! So do you, dude! Dude, what does my tattoo say? Sweet! What about mine? Dude! What does mine say? Sweet! What about mine? Dude! What does mine say? ``` ::tabs ::tab antlers ```antlers {{ quote | count_substring('dude') }} ``` ::tab blade ```blade {{ Statamic::modify($quote)->countSubstring('dude') }} ``` :: ```html 5 ``` ================================================ FILE: content/collections/modifiers/dashify.md ================================================ --- id: e50e2b3a-4377-4a74-b25a-d1ecf5d2d04a blueprint: modifiers modifier_types: - string title: Dashify --- Returns a lowercase and trimmed string separated by dashes. Dashes are inserted before uppercase characters (with the exception of the first character of the string), and in place of spaces as well as underscores. ```yaml title: Just Because I Can ``` ::tabs ::tab antlers ```antlers {{ title | dashify }} ``` ::tab blade ```blade {{ Statamic::modify($title)->dashify() }} ``` :: ```html just-because-i-can ``` ================================================ FILE: content/collections/modifiers/days_ago.md ================================================ --- id: 811c1cf5-797f-4e77-af92-fde6c03e96d2 blueprint: modifiers modifier_types: - date parse_content: true title: 'Days Ago' related_entries: - e73f1574-732e-4a74-be47-37e1fddb05d6 - 603701ba-5da7-4ec8-abe5-5bc9fe6861ea - 06027289-825e-4205-bd3a-f375e26ab81e - 7ba53a64-0266-4752-af5b-282a40dd11fa - 6ebb6c28-d1f3-4362-92a0-8a16b5c9cd51 - 6fcbfa5c-854e-4541-9955-505eca0d6bf7 - 811c1cf5-797f-4e77-af92-fde6c03e96d2 - 40578328-3288-4c54-a475-8afad19a37e6 --- Returns the number of days since a given date variable. Statamic will attempt to parse any string as a date, but try to keep it in the least ambiguous date format possible. ```yaml # Let's assume a server date of "December 31 2021" date: December 25 2021 ``` ::tabs ::tab antlers ```antlers {{ date | days_ago }} ``` ::tab blade ```blade` {{ Statamic::modify($date)->daysAgo() }} `` :: ```output 6 ``` ================================================ FILE: content/collections/modifiers/decode.md ================================================ --- id: 1fd780fd-ae92-4e73-9513-2b9c845976e9 blueprint: modifiers modifier_types: - utility title: Decode --- Convert all HTML entities to their applicable characters via PHP's [html_entity_decode()][decode] function. Will convert both double and single quotes. This is the opposite of the [entities][entities] modifier. ```yaml string: "I'll "eat" the <b>bacon</b> now"; ``` ::tabs ::tab antlers ```antlers {{ string | decode }} ``` ::tab blade ```blade {{ Statamic::modify($string)->decode() }} ``` :: ```html I'll "eat" the bacon now ``` [decode]: http://php.net/manual/en/function.html-entity-decode.php [entities]: /modifiers/entities ================================================ FILE: content/collections/modifiers/deslugify.md ================================================ --- id: 826517cc-7273-4045-bca2-fe5825fd9bda blueprint: modifiers modifier_types: - string title: Deslugify --- Replaces all hyphens and underscores in a string with spaces. The opposite of [dashify](dashify). ```yaml title: Just-Because-I-Can ``` ::tabs ::tab antlers ```antlers {{ title | deslugify }} ``` ::tab blade ```blade {{ Statamic::modify($title)->deslugify() }} ``` :: ```html Just Because I Can ``` ================================================ FILE: content/collections/modifiers/divide.md ================================================ --- id: fedbc5fc-2478-4fba-92ba-4004bc6e8845 blueprint: modifiers modifier_types: - math title: Divide --- Divide a value or another variable by your variable. Pass an integer or the name of a second variable as the parameter. ```yaml bacon: 21 skillets: 3 ``` ::tabs ::tab antlers ```antlers {{ bacon | divide($skillets) }} {{ skillets | divide(3) }} ``` ::tab blade ```blade {{ Statamic::modify($bacon)->divide($skillets) }} {{ Statamic::modify($skillets)->divide(3) }} ``` :: ```html 7 1 ``` ================================================ FILE: content/collections/modifiers/dl.md ================================================ --- id: fbdb7bf5-ac19-444c-9536-57332ffff388 blueprint: modifiers modifier_types: - array - markup title: DL --- Turn a key/value array, otherwise known as a YAML mapping, into an HTML definition list. ```yaml food: Delicious: - bacon - sushi Green: - broccoli - kale ``` ::tabs ::tab antlers ```antlers {{ food | dl }} ``` ::tab blade ```blade {!! Statamic::modify($food)->dl() !!} ``` :: ```html
Delicious
bacon
sushi
Green
broccoli
kale
``` ================================================ FILE: content/collections/modifiers/doesnt_overlap.md ================================================ --- id: 948e8351-70ba-4263-a3d5-34dfff0551d5 blueprint: modifiers modifier_types: - array - conditions title: "Doesn't Overlap" --- The inverse of [`overlaps`](/modifiers/overlaps). Returns `true` when _none_ of the needle values are found in the haystack array, otherwise `false`. The first parameter is the "needle" to compare against the "haystack". It will read from the context if there is a matching variable, otherwise it will use the parameter as the value. The needle can be a single value or an array. ```yaml shopping_list: - eggs - flour - beef jerky avoid: - kale - tofu ``` ::tabs ::tab antlers ```antlers {{ if shopping_list | doesnt_overlap('avoid') }} All clear! {{ /if }} {{ if shopping_list | doesnt_overlap('flour') }} Nope, there's flour. {{ /if }} ``` ::tab blade ```blade @if (Statamic::modify($shopping_list)->doesntOverlap('avoid')->fetch()) All clear! @endif @if (Statamic::modify($shopping_list)->doesntOverlap('flour')->fetch()) Nope, there's flour. @endif ``` :: ```html All clear! ``` ================================================ FILE: content/collections/modifiers/dump.md ================================================ --- id: 12de1a6c-e8be-4703-81a3-fc270311bc84 blueprint: modifiers modifier_types: - utility title: Dump --- Dump a variable to the browser and see under the hood with data types and array exportation. Definitely just for debugging when in development. ```yaml food: delicious: - bacon - sushi ``` ::tabs ::tab antlers ```antlers {{ food | dump }} ``` ::tab blade ```blade @dd($food) ``` :: ```html array:2 [▼ "delicious" => array:2 [▶] ] ``` :::tip You can also use the [dump tag](/tags/dump) to achieve a similar effect. ::: ================================================ FILE: content/collections/modifiers/embed_url.md ================================================ --- id: 45310885-fbd3-438d-85d5-076dda1646e0 blueprint: modifiers modifier_types: - string title: 'Embed Url' --- Converts a Youtube or Vimeo link to their embed URLs. Plays nicely with the [Video fieldtype](/fieldtypes/video) and the [is_embeddable modifier](/modifiers/is_embeddable). ```yaml youtube: https://www.youtube.com/watch?v=s9F5fhJQo34 vimeo: https://vimeo.com/22439234 other: http://example.com/video.mp4 ``` ::tabs ::tab antlers ```antlers {{ youtube | embed_url }} {{ vimeo | embed_url }} {{ other | embed_url }} ``` ::tab blade ```blade {{ Statamic::modify($youtube)->embedUrl() }} {{ Statamic::modify($vimeo)->embedUrl() }} {{ Statamic::modify($other)->embedUrl() }} ``` :: ```html https://www.youtube.com/embed/s9F5fhJQo34 https://player.vimeo.com/video/22439234 http://example.com/video.mp4 ``` ================================================ FILE: content/collections/modifiers/ends_with.md ================================================ --- id: 40fc5b1e-d0e7-488a-bf6e-e6ef4a8b7dd8 blueprint: modifiers modifier_types: - conditions title: 'Ends With' --- Returns `true` if the value ends with a given string. This comparison is case-insensitive. ```yaml punchline: That's what she said! ``` ::tabs ::tab antlers ```antlers {{ if (punchline | ends_with('she said!')) }} {{ if (punchline | ends_with('your mom!')) }} ``` ::tab blade ```blade @if (Statamic::modify($punchline)->endsWith('she said!')->fetch()) @endif @if (Statamic::modify($punchline)->endsWith('your mom!')->fetch()) @endif ``` :: ```html true false ``` ================================================ FILE: content/collections/modifiers/ensure_left.md ================================================ --- id: 171d9329-c456-45ca-a5e4-fc5bad7fb0ec blueprint: modifiers modifier_types: - string - utility title: 'Ensure Left' --- Ensures that the string begins with a specified string. If it doesn't, it will now. ```yaml links: - statamic.com - http://wilderborn.com ``` ::tabs ::tab antlers ```antlers {{ links }}
  • {{ value | ensure_left('http://') }}
  • {{ /links }} ``` ::tab blade ```blade @foreach ($links as $link)
  • {{ Statamic::modify($link)->ensureLeft('http://') }}
  • @endforeach ``` :: ```html
  • http://statamic.com
  • http://wilderborn.com
  • ``` ================================================ FILE: content/collections/modifiers/ensure_right.md ================================================ --- id: 6854539a-4661-483b-bb1f-2d28df0db76e blueprint: modifiers modifier_types: - string - utility title: 'Ensure Right' --- Ensures that the string ends with a specified string. If it doesn't, it will now. ```yaml links: - statamic - wilderborn.com ``` ::tabs ::tab antlers ```antlers {{ links }}
  • {{ value | ensure_right('.com') }}
  • {{ /links }} ``` ::tab blade ```blade @foreach ($links as $link)
  • {{ Statamic::modify($value)->ensureRight('.com') }}
  • @endforeach ``` :: ```html
  • statamic.com
  • wilderborn.com
  • ``` ================================================ FILE: content/collections/modifiers/entities.md ================================================ --- id: 50c06e32-9b94-4129-85ba-7cc4201b9e3f blueprint: modifiers modifier_types: - string - utility title: Entities --- Encode a string with HTML entities via PHP's [htmlentities()][entities] function. This is the opposite of the [decode][decode] modifier. ```yaml string: "The 'bacon' is crispy" ``` ::tabs ::tab antlers ```antlers {{ string | entities }} ``` ::tab blade ```blade {{ Statamic::modify($string)->entities() }} ``` :: ```html The 'bacon' is <b>crispy</b> ``` [entities]: http://php.net/manual/en/function.htmlentities.php [decode]: /modifiers/decode ================================================ FILE: content/collections/modifiers/excerpt.md ================================================ --- id: 051ecd7b-1cf7-4b47-b8c1-cfcede33289f blueprint: modifiers title: Excerpt intro: 'Generate an excerpt from your content.' modifier_types: - string --- Breaks a string at a given marker. Uses `` by default. ```yaml --- title: 'Example Entry' --- Lorem Ipsum dolor sit amet. consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Massa id neque aliquam vestibulum. ``` ::tabs ::tab antlers ```antlers {{ books | excerpt }} ``` ::tab blade ```blade {{ Statamic::modify($books)->excerpt() }} ``` :: ```html Lorem Ipsum dolor sit amet. ``` You can override the marker by passing an alternative as the first parameter: ``` {{ books | excerpt: }} ``` ================================================ FILE: content/collections/modifiers/explode.md ================================================ --- id: c3d3e2c4-218e-4841-a594-647f10863866 blueprint: modifiers modifier_types: - string - array title: Explode --- Breaks a string into an array of strings split on a given delimiter. ```yaml places: Scotland, England, Switzerland, Italy ``` ::tabs ::tab antlers ```antlers {{ places | explode(',') | ul }} ``` ::tab blade ```blade {!! Statamic::modify($places)->explode(',')->ul() !!} ``` :: ```html
    • Scotland
    • England
    • Switzerland
    • Italy
    ``` To limit the number of splits, pass the limit as the second argument: ::tabs ::tab antlers ```antlers {{ places | explode(',', 2) | ul }} ``` ::tab blade ```blade {!! Statamic::modify($places)->explode(',', 2)->ul() !!} ``` :: ```html
    • Scotland
    • England, Switzerland, Italy
    ``` ================================================ FILE: content/collections/modifiers/favicon.md ================================================ --- id: 9c7ad945-2d02-423b-ae97-09cbe7ffed0d blueprint: modifiers modifier_types: - markup attributes: true title: Favicon --- Given a valid URL will generate a proper favicon meta tag. ```yaml icon: /assets/img/favicon.png ``` ::tabs ::tab antlers ```antlers {{ icon | favicon }} ``` ::tab blade ```blade {!! Statamic::modify($icon)->favicon() !!} ``` :: ```html ``` ================================================ FILE: content/collections/modifiers/filter_empty.md ================================================ --- id: a01e28a5-7c59-436c-8137-98e9481631ba modifier_types: - array title: 'Filter Empty' --- Filters out null values from an array. ```yaml favorite_things: - pizza - null - ice cream ``` ```antlers {{ favorite_things | filter_empty }} ``` ```output pizza ice cream ``` ================================================ FILE: content/collections/modifiers/first.md ================================================ --- id: 3ef1e731-742e-48c2-81f0-6fa916ecda0a blueprint: modifiers modifier_types: - markup - string - utility - array title: First --- Returns the first X characters of a string, where X is any positive integer, or the first item in an array. ```yaml title: 2015 Year Books Photos array: - Sonic - Knuckles - Tails ``` ::tabs ::tab antlers ```antlers {{ title | first(4) }} {{ array | first }} ``` ::tab blade ```blade {{ Statamic::modify($title)->first(4) }} {{ Statamic::modify($array)->first() }} ``` :: ```html 2015 Sonic ``` ================================================ FILE: content/collections/modifiers/flatten.md ================================================ --- id: e893345c-03f7-466b-a400-bbd2545bd780 blueprint: modifiers modifier_types: - array - utility title: Flatten --- Flattens a multi-dimensional array (a Grid or Replicator field for example) into a single dimension. ```yaml ingredients: spices: [garlic, cumin, ginger, turmeric, paprika, curry powder] vegetables: [tomatoes, onion] meat: [chicken] ``` ::tabs ::tab antlers ```antlers {{ ingredients | flatten }} ``` ::tab blade ```blade flatten() ->fetch(); ?> ``` :: ```yaml ingredients: - garlic - cumin - ginger - turmeric - paprika - curry powder - tomatoes - onion - chicken ``` You can optionally pass a `depth` parameter to the `flatten` modifier, allowing you to specify how deeply nested arrays should be flattened. ```yaml - - garlic - cumin - ginger - turmeric - paprika - curry powder - - tomatoes - onion - - chicken ``` ================================================ FILE: content/collections/modifiers/flip.md ================================================ --- id: f874e969-d579-4501-9140-e4005945d302 blueprint: modifiers modifier_types: - array title: Flip --- Swaps the keys with their corresponding values. The old switcharoo. ```yaml favorites: food: burger drink: soda ``` ::tabs ::tab antlers ```antlers {{ favorites | json }} {{ favorites | flip | json }} ``` ::tab blade ```blade {!! Statamic::modify($favorites)->json() !!} {!! Statamic::modify($favorites)->flip()->json() !!} ``` :: ```json {"food":"burger","drink":"soda"} {"burger":"food","soda":"drink"} ``` ================================================ FILE: content/collections/modifiers/floor.md ================================================ --- id: 0dc57cca-67b2-45a1-a02d-915ac64f064f blueprint: modifiers modifier_types: - math - utility title: Floor --- Rounds a number down to the next whole number. ```yaml number: 25.98 ``` ::tabs ::tab antlers ```antlers {{ number | floor }} ``` ::tab blade ```blade {{ Statamic::modify($number)->floor() }} ``` :: ```html 25 ``` ================================================ FILE: content/collections/modifiers/folder.yaml ================================================ order: alphabetical parse_content: false template: modifier ================================================ FILE: content/collections/modifiers/format.md ================================================ --- id: 756d23b4-209c-457c-b9f5-d69347bbe8fe blueprint: modifiers modifier_types: - date - string title: Format --- Given a date string, or anything that sort of looks like a date string, `format` will convert it to a [Carbon][carbon] instance and allow you to format it with PHP's [datetime format][datetime] variables. ```yaml event_date: April 15 2016 ``` ::tabs ::tab antlers ```antlers {{ event_date | format('Y-m-d') }} ``` ::tab blade ```blade {{ Statamic::modify($event_date)->format('Y-m-d') }} ``` :: ```html 2016-04-15 ``` :::warning By default, when using a modifier on a date variable, it will be operating on the UTC date rather than the localized date. Please refer to our [Timezones](/tips/timezones) guide for more information. ::: ## Parameters ### Day | Character | Description | Example | | --------- | ----------- | -------------- | | `d` | Day of the month, 2 digits with leading zeros | `01` to `31` | | `D` | A textual representation of a day, three letters | `Mon` to `Sun` | | `j` | Day of the month without leading zeros | `1` to `31` | | `l` | A full textual representation of the day of the week | `Sunday` to `Saturday`| | `N` | ISO 8601 numeric representation of the day of the week | `1` (for Monday) to `7` (for Sunday) | | `S` | English ordinal suffix for the day of the month, 2 characters | `st`, `nd`, `rd` or `th`. Works well with `j` | | `w` | Numeric representation of the day of the week | `0` (for Sunday) to `6` (for Saturday) | | `z` | The day of the year (starting from 0) | `0` to `365` | ### Week | Character | Description | Example | | --------- | ----------- | -------------- | | `W` | ISO 8601 week number of year, weeks starting on Monday | `42` (the 42nd week in the year) | ### Month | Value | Description | Example | | --------- | ----------- | -------------- | | `F` | A full textual representation of a month, such as January or March | `January` to `December` | | `m` | Numeric representation of a month, with leading zeros | `01` to `12` | | `M` | A short textual representation of a month, three letters | `Jan` to `Dec` | | `n` | Numeric representation of a month, without leading zeros | `1` to `12` | | `t` | Number of days in the given month | `28` to `31` | ### Year | Value | Description | Example | | --------- | ----------- | -------------- | | `L` | Whether it's a leap year | `1` if it is a leap year, `0` otherwise. | | `o` | ISO 8601 week-numbering year. | `1999` or `2003` | | `Y` | A full numeric representation of a year, at least 4 digits, with `-` for years BCE.| `-0055`, `0787`, `1999`, `2003` | | `y` | A two digit representation of a year | `99` or `03` | ### Time | Value | Description | Example | | --------- | ----------- | -------------- | | `a` | Lowercase Ante meridiem and Post meridiem | `am` or `pm` | | `A` | Uppercase Ante meridiem and Post meridiem | `AM` or `PM` | | `B` | Swatch Internet time (it's coming back, just you wait) | `000` to `999` | | `g` | 12-hour format of an hour without leading zeros | `1` to `12` | | `G` | 24-hour format of an hour without leading zeros | `0` to `23` | | `h` | 12-hour format of an hour with leading zeros | `01` to `12` | | `H` | 24-hour format of an hour with leading zeros | `00` to `23` | | `i` | Minutes with leading zeros | `00` to `59` | | `s` | Seconds with leading zeros | `00` to `59` | | `u` | Microseconds. | `654321` | | `v` | Milliseconds. Same note applies as for `u`.| `654` | ### Timezone | Value | Description | Example | | --------- | ----------- | -------------- | | `e` | Timezone identifier | `UTC`, `GMT`, `Atlantic/Azores` | | `I`  | Whether or not the date is in daylight saving time | `1` if Daylight Saving Time, `0` otherwise. | | `O` | Difference to Greenwich time (GMT) without colon between hours and minutes | `+0200` | | `P` | Difference to Greenwich time (GMT) with colon between hours and minutes | `+02:00` | | `p` | The same as `P`, but returns `Z` instead of `+00:00` | `+02:00` | | `T` | Timezone abbreviation, if known; otherwise the GMT offset. | `EST`, `MDT`, `+05` | | `Z` | Timezone offset in seconds. The offset for timezones west of UTC is always negative, and for those east of UTC is always positive. | `-43200` to `50400` | ### Full Date/Time | Value | Description | Example | | --------- | ----------- | -------------- | | `c` | ISO 8601 date | 2004-02-12T15:19:21+00:00 | | `r` | [RFC 2822](http://www.faqs.org/rfcs/rfc2822) or [RFC 5322](http://www.faqs.org/rfcs/rfc5322) formatted date | `Thu, 21 Dec 2000 16:01:07 +0200` | [carbon]: http://carbon.nesbot.com [datetime]: https://www.php.net/manual/en/datetime.format.php ================================================ FILE: content/collections/modifiers/format_number.md ================================================ --- id: 63b56419-6556-4174-8d26-e941460b82a4 blueprint: modifiers modifier_types: - math - number title: 'Format Number' --- Format a number with grouped thousands and decimal points. In other words, make it look nice. - Parameter 1: precision (number of decimal places before rounding) - Parameter 2: Decimal point (default `.`) - Parameter 3: Thousands separator (default: `,`) ```yaml lucky_number: 130134.109 ``` ::tabs ::tab antlers ```antlers {{ lucky_number | format_number(1, ',', ',') }} ``` ::tab blade ```blade {{ Statamic::modify($lucky_number)->formatNumber(1, ',', ',') }} ``` :: ```html 130,134,1 ``` ================================================ FILE: content/collections/modifiers/format_translated.md ================================================ --- id: 8cbea367-799f-4fb6-866e-519d571f7b3e blueprint: modifiers modifier_types: - date - string title: 'Format Translated' --- Given a date string, or anything that sort of looks like a date string, `format_translated` will convert it to a [Carbon][carbon] instance and allow you to format it using your site's configured locale. ```yaml event_date: 2024-02-28 ``` ::tabs ::tab antlers ```antlers {{ event_date | format_translated('l j F Y') }} ``` ::tab blade ```blade {{ Statamic::modify($event_date)->format_translated('l j F Y') }} ``` :: Assuming your site's locale is `fr_FR`: ```html mercredi 28 février 2024 ``` :::warning By default, when using a modifier on a date variable, it will be operating on the UTC date rather than the localized date. Please refer to our [Timezones](/tips/timezones) guide for more information. ::: [carbon]: http://carbon.nesbot.com ================================================ FILE: content/collections/modifiers/full_urls.md ================================================ --- id: 44cb7965-877e-49b3-92fe-a24970b542a2 blueprint: modifiers modifier_types: - string - utility title: 'Full Urls' --- Replaces root-relative URLs inside HTML attributes (e.g. `href` and `src` ) with absolute URLs. This is most often used in RSS feeds and other places where markup may be consumed off the domain. ```html I had this totally crazy dream last night and I know you want to hear all about it! ``` ::tabs ::tab antlers ```antlers {{ content | full_urls }} ``` ::tab blade ```blade {!! Statamic::modify($content)->fullUrls() !!} ``` :: ```html I had this totally crazy dream last night and I know you want to hear all about it! ``` ================================================ FILE: content/collections/modifiers/get.md ================================================ --- id: c6311d04-364d-4086-8b6b-2a58e88c6cb8 blueprint: modifiers title: Get modifier_types: - asset - utility - relationship --- Gets a value from a relationship based on its ID. This is like a nicer-to-read single tag version of the [Get_Content Tag](/tags/get_content). ```yaml featured_post: 4e82a520-275f-11e6-bdf4-0800200c9a66 ``` ::tabs ::tab antlers ```antlers {{ featured_post | get('title') }} ``` ::tab blade ```blade {{ Statamic::modify($featured_post)->get('title') }} ``` :: ```html Featured Post Title ``` The above is equivalent to doing this: ::tabs ::tab antlers ```antlers {{ get_content :from="featured_post" }} {{ title }} {{ /get_content }} ``` ::tab blade ```blade {{ $title }} ``` :: ================================================ FILE: content/collections/modifiers/gravatar.md ================================================ --- id: c0a376c1-1ade-447b-8a2d-18722a5446ba blueprint: modifiers modifier_types: - string - utility title: Gravatar --- Converts an email string to a Gravatar image URL. The size can be specified by a parameter. ```yaml email: rswanson@inpra.org ``` ::tabs ::tab antlers ```antlers {{ email | gravatar }} {{ email | gravatar(80) }} ``` ::tab blade ```blade {{ Statamic::modify($email)->gravatar() }} {{ Statamic::modify($email)->gravatar(80) }} ``` :: ```html https://www.gravatar.com/avatar/f4650388367dc01cf2acf16b412b3966 https://www.gravatar.com/avatar/f4650388367dc01cf2acf16b412b3966?s=80 ``` ================================================ FILE: content/collections/modifiers/group_by.md ================================================ --- id: a070fabe-c413-4b31-9cb4-ad14bbe1aa4d blueprint: modifiers modifier_types: - array title: 'Group By' --- ## Overview You may use this modifier to group items (a simple array, a collection of entries, etc) into groups based on some common value. ## By Key The most basic usage example would be to take a simple array and output the groups using the key. ```yaml sponsors: - sport: basketball team: Jazz - sport: baseball team: Yankees - sport: basketball team: Bulls ``` ``` {{ sponsors | group_by('sport') }}

    Basketball

    {{ basketball }} {{ team }} {{ /basketball }}

    Baseball

    {{ baseball }} {{ team }} {{ /baseball }} {{ /sponsors }} ``` ```html

    Basketball

    Jazz Bulls

    Baseball

    Yankees ``` ## Looping Over Groups In the previous example, you had to know that there was going to be `basketball` and `baseball` keys ahead of time. If you don't know the groups, you can loop over the `groups` variable. It will be an array containing the name of the `group` and its `items`. ``` {{ sponsors group_by="sport" }} {{ groups }}

    {{ group | upper }}

    {{ items }} {{ team }} {{ /items }} {{ /groups }} {{ /sponsors }} ``` ## Nested Values If you need to get a nested value for the groups, you can use the familiar colon syntax. For example, you may have an `entries` field where you've selected entries from multiple collections, and you want to group by the collection's title. ```yaml menu_items: - burger - fries - coke - pepsi ``` ``` {{ menu_items group_by="collection:title" }} {{ groups }}

    {{ group }}

    {{ items }} {{ title }} {{ /items }} {{ /groups }} {{ /menu_items }} ``` ```

    Food

    Burger Fries

    Drinks

    Coke Pepsi ``` ## Dates You may group entries by a date field. For example, you might want to output articles and group them by month. Here we'll group them by the `date` field using the `F Y` [PHP date format](https://www.php.net/manual/en/datetime.format.php) which would output as "Month Year". ``` {{ collection:articles as="entries" }} {{ entries group_by="date|F Y" }} {{ groups }}

    {{ group }}

    {{ items }} {{ title }} {{ /items }} {{ /groups }} {{ /entries }} {{ /collection:articles }} ``` ```

    September 2021

    Entry from September Another entry from September

    October 2021

    Entry from October ``` :::tip The date field in this example is named `date`, but you can use any date field. ``` {{ entries group_by="custom_date_field|F Y" }} ``` ::: If you need the key to differ from how it's displayed (perhaps you want to use an additional modifier after), you can pass another date format as the third argument. (Argument 2 creates the key, argument 3 creates the `{{ group }}` text). ``` {{ entries group_by="date|Y-m|F Y" }} ``` ================================================ FILE: content/collections/modifiers/has_lower_case.md ================================================ --- id: a5ce6691-840c-4bf7-b5f4-b87bb4845055 blueprint: modifiers modifier_types: - conditions title: 'Has Lower Case' --- Returns `true` if the string contains a lowercase character, `false` otherwise. ```yaml loud_noises: "I DON'T KNOW WHAT WE'RE YELLING ABOUT!" ``` ::tabs ::tab antlers ```antlers {{ if loud_noises | has_lower_case }} ``` ::tab blade ```blade @if (Statamic::modify($loud_noises)->hasLowerCase()->fetch()) @endif ``` :: ```html false ``` ================================================ FILE: content/collections/modifiers/has_upper_case.md ================================================ --- id: b2936dd3-f8b1-44a7-bb40-f3b0a8b47e90 blueprint: modifiers modifier_types: - conditions title: 'Has Upper Case' --- Returns `true` if the string contains an uppercase character, `false` otherwise. ```yaml loud_noises: "I DON'T KNOW WHAT WE'RE YELLING ABOUT!" ``` ::tabs ::tab antlers ```antlers {{ if loud_noises | has_upper_case }} ``` ::tab blade ```blade @if (Statamic::modify($loud_noises)->hasUpperCase()->fetch()) @endif ``` :: ```html true ``` ================================================ FILE: content/collections/modifiers/headline.md ================================================ --- id: 2d555b32-e68c-4f9b-8570-f2e8d185989b blueprint: modifiers modifier_types: - markup - string - utility title: Headline --- Format the given string, usually a headline or title, with either [AP](https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case) or [MLA](https://style.mla.org/capitalization-of-titles/) style. Accepts `ap` or `mla` as an argument. Defaults to `ap` if none is specified. ```yaml title: I see a bad-ass mother who don't take no crap off of nobody. ``` ::tabs ::tab antlers ```antlers {{ title | headline('ap') }} ``` ::tab blade ```blade {{ Statamic::modify($title)->headline('ap') }} ``` :: ``` I See a Bad-Ass Mother Who Don't Take No Crap Off of Nobody. ``` ::tabs ::tab antlers ```antlers {{ title | headline('mla') }} ``` ::tab blade ```blade {{ Statamic::modify($title)->headline('mla') }} ``` :: ``` I See a Bad-ass Mother Who Don't Take No Crap Off of Nobody. ``` ================================================ FILE: content/collections/modifiers/hex_to_rgb.md ================================================ --- id: 69000ef1-0a98-42a4-ba1a-0bab4c58ca7d blueprint: modifiers modifier_types: - utility title: 'Hex To RGB' --- Converts a color from hex to RGB, the perfect match for the [color fieldtype](/fieldtypes/color). ```yaml color: `#FF269E` ``` ::tabs ::tab antlers ```antlers {{ color | hex_to_rgb }} ``` ::tab blade ```blade {{ Statamic::modify($color)->hexToRgb() }} ``` :: ```html 255, 38, 158 ``` ================================================ FILE: content/collections/modifiers/hours_ago.md ================================================ --- id: 6ebb6c28-d1f3-4362-92a0-8a16b5c9cd51 blueprint: modifiers modifier_types: - date title: 'Hours Ago' related_entries: - e73f1574-732e-4a74-be47-37e1fddb05d6 - 603701ba-5da7-4ec8-abe5-5bc9fe6861ea - 06027289-825e-4205-bd3a-f375e26ab81e - 7ba53a64-0266-4752-af5b-282a40dd11fa - 6fcbfa5c-854e-4541-9955-505eca0d6bf7 - 811c1cf5-797f-4e77-af92-fde6c03e96d2 --- Returns the number of hours since a given date variable. Statamic will attempt to parse any string as a date, but try to keep it in the least ambiguous date format possible. ```yaml date: October 1 2015 ``` ::tabs ::tab antlers ```antlers {{ date | hours_ago }} ``` ::tab blade ```blade {{ Statamic::modify($date)->hoursAgo() }} ``` :: ```html {{ test_date | hours_ago }} ``` ================================================ FILE: content/collections/modifiers/image.md ================================================ --- id: 26045669-567d-4e93-b3ba-34c835f5c5e9 blueprint: modifiers modifier_types: - asset - markup attributes: true title: Image --- Generate an HTML image element with the variable's value as `src`. ```yaml header_image: /assets/img/bokeh-bunnies.jpg ``` ::tabs ::tab antlers ```antlers {{ header_image | image }} ``` ::tab blade ```blade {!! Statamic::modify($header_image)->image() !!} ``` :: ```html ``` ================================================ FILE: content/collections/modifiers/in_array.md ================================================ --- id: 4e349523-cba6-4f3b-a0e1-bd4e8b1cf6b9 blueprint: modifiers modifier_types: - array - utility - conditions title: 'In Array' --- Check if an array contains a specific value. Returns `true` if a match is found. The first parameter is the "needle" to find in the "haystack". It will read from the context if there is a matching variable, otherwise it will use the parameter as the value. You can pass multiple arguments. ```yaml shopping_list: - eggs - flour - beef jerky want: eggs ``` ::tabs ::tab antlers ```antlers {{ if (shopping_list | in_array('flour')) }} GOT IT! {{ /if }} {{ if (shopping_list | in_array('want')) }} GOT EM! {{ /if }} {{ if (shopping_list | in_array('eggs', 'flour')) }} YES I DID NOT FORGET! {{ /if }} ``` ::tab blade ```blade @if (Statamic::modify($shopping_list)->inArray('flour')->fetch()) GOT IT! @endif @if (Statamic::modify($shopping_list)->inArray('want')->fetch()) GOT EM! @endif @if (Statamic::modify($shopping_list)->inArray('eggs', 'flour')->fetch()) YES I DID NOT FORGET! @endif ``` :: ```html GOT IT! GOT EM! YES I DID NOT FORGET! ``` ================================================ FILE: content/collections/modifiers/insert.md ================================================ --- id: fa936f49-be25-432e-9543-7a201a652055 blueprint: modifiers modifier_types: - string - utility title: Insert --- Inserts a string at the position provided. The beginning of the string is position 0. ```yaml opinion: This is yummy. ``` ::tabs ::tab antlers ```antlers {{ opinion | insert('not', 8) }} ``` ::tab blade ```blade {{ Statamic::modify($opinion)->insert(['not', 8]) }} ``` :: ```html This is not yummy. ``` ================================================ FILE: content/collections/modifiers/is_after.md ================================================ --- id: 3c167645-5ad1-45b4-b6df-22b0d2c95abf blueprint: modifiers modifier_types: - date - conditions title: 'Is After' --- Returns `true` if a date variable is after another date. That second date can be the name of another variable or a literal date string. ```yaml start_date: January 17 2015 end_date: December 1 2015 ``` ::tabs ::tab antlers ```antlers {{ if end_date | is_after($start_date) }} {{ if start_date | is_after(2014) }} {{ if start_date | is_after($end_date) }} ``` ::tab blade ```blade @if (Statamic::modify($end_date)->isAfter($start_date)->fetch()) @endif @if (Statamic::modify($start_date)->isAfter(2014)->fetch()) @endif @if (Statamic::modify($start_date)->isAfter($end_date)->fetch()) @endif ``` :: ```html true true false ``` :::warning By default, when using a modifier on a date variable, it will be operating on the UTC date rather than the localized date. Please refer to our [Timezones](/tips/timezones) guide for more information. ::: ================================================ FILE: content/collections/modifiers/is_alpha.md ================================================ --- id: a6aaac80-19b7-4400-af21-9147aff064c4 blueprint: modifiers modifier_types: - string - conditions title: 'Is Alpha' --- Returns `true` if string contains **only** alphabetic characters. Numbers, punctuation, whitespace, and another other special characters will cause a `false`. ```yaml secret_phrase: abcdefg even_more_secret_phrase: abc123 ``` ::tabs ::tab antlers ```antlers {{ if secret_phrase | is_alpha }} {{ if even_more_secret_phrase | is_alpha }} ``` ::tab blade ```blade @if (Statamic::modify($secret_phrase)->isAlpha()->fetch()) @endif @if (Statamic::modify($even_more_secret_phrase)->isAlpha()->fetch()) @endif ``` :: ```html true false ``` ================================================ FILE: content/collections/modifiers/is_alphanumeric.md ================================================ --- id: 923f34bd-d17d-4353-821f-48e986bdce3b blueprint: modifiers modifier_types: - number - string - conditions title: 'Is Alphanumeric' --- Returns `true` if string contains **only** alphanumeric characters. Punctuation, whitespace, and another other special characters will cause a `false`. ```yaml secret_phrase: abc123 even_more_secret_phrase: abc123!@# ``` ::tabs ::tab antlers: ```antlers {{ if secret_phrase | is_alphanumeric }} {{ if even_more_secret_phrase | is_alphanumeric }} ``` ::tab blade ```blade @if (Statamic::modify($secret_phrase)->isAlphanumeric()->fetch()) @endif @if (Statamic::modify($even_more_secret_phrase)->isAlphanumeric()->fetch()) @endif ``` :: ```html true false ``` ================================================ FILE: content/collections/modifiers/is_array.md ================================================ --- id: 1ac30316-8467-425e-9a50-82e754be7df2 blueprint: modifiers modifier_types: - array - utility - conditions title: 'Is Array' --- Check if a value is an array. Returns a `bool`. The modifier is a wrapper around PHP's [built-in](https://www.php.net/manual/en/function.is-array) `is_array` function. ================================================ FILE: content/collections/modifiers/is_before.md ================================================ --- id: 85b0a5d9-eb77-4bc2-b60e-7b4d3f9aa406 blueprint: modifiers modifier_types: - date - conditions title: 'Is Before' --- Returns `true` if a date variable is before another date. That second date can be the name of another variable or a literal date string. ```yaml start_date: January 17 2015 end_date: December 1 2015 ``` ::tabs ::tab antlers ```antlers {{ if end_date | is_before($start_date) }} {{ if start_date | is_before(2014) }} {{ if start_date | is_before($end_date) }} ``` ::tab blade ```blade @if (Statamic::modify($end_date)->isBefore($start_date)->fetch()) @endif @if (Statamic::modify($start_date)->isBefore(2014)->fetch()) @endif @if (Statamic::modify($start_date)->isBefore($end_date)->fetch()) @endif ``` :: ```html false false true ``` :::warning By default, when using a modifier on a date variable, it will be operating on the UTC date rather than the localized date. Please refer to our [Timezones](/tips/timezones) guide for more information. ::: ================================================ FILE: content/collections/modifiers/is_between.md ================================================ --- id: cfbf8926-47ee-42e1-a972-86e3dc13633b blueprint: modifiers modifier_types: - conditions - date - number title: 'Is Between' --- Returns `true` if a date variable is between two other dates. Those dates can be the name of other variables or literal date strings. ```yaml date: November 15 2015 start_date: July 4 2015 end_date: December 1 2015 ``` ::tabs ::tab antlers ```antlers {{ if date | is_between($start_date, $end_date) }} ``` ::tab blade ```blade @if (Statamic::modify($date)->isBetween([$start_date, $end_date])->fetch()) @endif ``` :: ```html true ``` :::warning By default, when using a modifier on a date variable, it will be operating on the UTC date rather than the localized date. Please refer to our [Timezones](/tips/timezones) guide for more information. ::: ================================================ FILE: content/collections/modifiers/is_blank.md ================================================ --- id: 054a230c-a655-48b0-af8b-a963bb0b89b0 blueprint: modifiers modifier_types: - conditions title: 'Is Blank' --- Returns `true` if the string contains only whitespace chars. ```yaml ghost: zombie: BRAINSSSS ``` ::tabs ::tab antlers ```antlers {{ if ghost | is_blank }} {{ if zombie | is_blank }} ``` ::tab blade ```blade @if (Statamic::modify($ghost)->isBlank()->fetch()) @endif @if (Statamic::modify($zombie)->isBlank()->fetch()) @endif ``` :: ```html true false ``` ================================================ FILE: content/collections/modifiers/is_email.md ================================================ --- id: 471b3281-4573-43ee-9fee-eb55edf26ad0 blueprint: modifiers modifier_types: - string - conditions title: 'Is Email' --- Returns `true` if a string is a valid email address. ```yaml an_email: lknope@inpra.org not_an_email: waffles ``` ::tabs ::tab antlers ```antlers {{ if an_email | is_email }} {{ if not_an_email | is_email }} ``` ::tab blade ```blade @if (Statamic::modify($an_email)->isEmail()->fetch()) @endif @if (Statamic::modify($not_an_email)->isEmail()->fetch()) @endif ``` :: ```html true false ``` ================================================ FILE: content/collections/modifiers/is_embeddable.md ================================================ --- id: d3ab4cb6-2aeb-4a15-93b9-79e56ad82223 blueprint: modifiers modifier_types: - string title: 'Is Embeddable' --- Checks to see if a video URL is embeddable. In other words: YouTube or Vimeo URL are considered as embeddable. Plays nicely with the [Video fieldtype](/fieldtypes/video) and the [embed_url modifier](/modifiers/embed_url). ```yaml youtube: https://www.youtube.com/watch?v=s9F5fhJQo34 vimeo: https://vimeo.com/22439234 other: http://example.com/video.mp4 ``` ::tabs ::tab antlers ```antlers {{ youtube | is_embeddable }} {{ vimeo | is_embeddable }} {{ other | is_embeddable }} ``` ::tab blade ```blade @if (Statamic::modify($youtube)->isEmbeddable()->fetch()) ... @endif @if (Statamic::modify($vimeo)->isEmbeddable()->fetch()) ... @endif @if (Statamic::modify($other)->isEmbeddable()->fetch()) ... @endif ``` :: ```html true true false ``` ================================================ FILE: content/collections/modifiers/is_empty.md ================================================ --- id: a94a24ce-500d-4194-85db-85fcbb552e06 blueprint: modifiers modifier_types: - array title: 'Is Empty' --- Checks to see if an array is empty without any set values. Works with numeric indexes, associative, and string keyed arrays of all depths. It's pretty smart, as these things go. ```yaml some_data: - is living here more_data: with: hopes: and: dreams: - ``` ::tabs ::tab antlers ```antlers {{ if some_data | is_empty }} {{ if more_data | is_empty }} ``` ::tab blade ```blade @if (Statamic::modify($some_data)->isEmpty()->fetch()) ... @endif @if (Statamic::modify($more_data)->isEmpty()->fetch()) ... @endif ``` :: ```html false true ``` ================================================ FILE: content/collections/modifiers/is_external_url.md ================================================ --- id: 4cb0f243-72f4-45a1-83d3-d72209907875 blueprint: modifiers modifier_types: - string - conditions title: 'Is External Url' --- Returns `true` if a string is an external URL. ```yaml google_url: http://google.com/ entry_url: /waffles not_a_url: bacon ``` ::tabs ::tab antlers ```antlers {{ if google_url | is_external_url }} {{ if entry_url | is_external_url }} {{ if not_a_url | is_external_url }} ``` ::tab blade ```blade @if (Statamic::modify($google_url)->isExternalUrl()->fetch()) ... @endif @if (Statamic::modify($entry_url)->isExternalUrl()->fetch()) ... @endif @if (Statamic::modify($not_a_url)->isExternalUrl()->fetch()) ... @endif ``` :: ```html true false false ``` ================================================ FILE: content/collections/modifiers/is_future.md ================================================ --- id: 26bf98af-5bc1-4ec9-b533-815872606e3b blueprint: modifiers modifier_types: - date - conditions title: 'Is Future' --- Returns `true` if date is in the future. ```yaml date: October 21 2015 another_date: November 2030 ``` ::tabs ::tab antlers ```antlers {{ if date | is_future }} {{ if another_date | is_future }} ``` ::tab blade ```blade @if (Statamic::modify($date)->isFuture()->fetch()) ... @endif @if (Statamic::modify($another_date)->isFuture()->fetch()) ... @endif ``` :: ```html false true ``` :::warning By default, when using a modifier on a date variable, it will be operating on the UTC date rather than the localized date. Please refer to our [Timezones](/tips/timezones) guide for more information. ::: ================================================ FILE: content/collections/modifiers/is_json.md ================================================ --- id: a314e7fc-ad72-4afb-88b8-1ca4a0100c17 blueprint: modifiers modifier_types: - conditions - utility title: 'Is Json' --- Returns `true` if string is valid json ```yaml data: '{"book": "All The Places You'll Go"}' ``` ::tabs ::tab antlers ```antlers {{ if data | is_json }} ``` ::tab blade ```blade @if (Statamic::modify($data)->isJson()->fetch()) ... @endif ``` :: ```html true ``` ================================================ FILE: content/collections/modifiers/is_leap_year.md ================================================ --- id: 0438995d-7100-4a72-9c0c-985e00f482bb blueprint: modifiers modifier_types: - date - conditions title: 'Is Leap Year' --- Returns `true` if date is in a leap year. Try and find a regular use for this one, we dare you. ```yaml date: November 2016 another_date: November 2017 ``` ::tabs ::tab antlers ```antlers {{ if date | is_leap_year }} {{ if another_date | is_leap_year }} ``` ::tab blade ```blade @if (Statamic::modify($date)->isLeapYear()->fetch()) ... @endif @if (Statamic::modify($another_date)->isLeapYear()->fetch()) ... @endif ``` :: ```html true false ``` :::warning By default, when using a modifier on a date variable, it will be operating on the UTC date rather than the localized date. Please refer to our [Timezones](/tips/timezones) guide for more information. ::: ================================================ FILE: content/collections/modifiers/is_lowercase.md ================================================ --- id: 97ff9a80-1e19-4f6d-b9e8-b5f4223e19d7 blueprint: modifiers modifier_types: - string - conditions title: 'Is Lowercase' --- Returns `true` if string is only lowercase characters. ```yaml topic: fhqwhgads from: Sibbie ``` ::tabs ::tab antlers ```antlers {{ if topic | is_lowercase }} {{ if from | is_lowercase }} ``` ::tab blade ```blade @if (Statamic::modify($topic)->isLowercase()->fetch()) ... @endif @if (Statamic::modify($from)->isLowercase()->fetch()) ... @endif ``` :: ```html true false ``` ================================================ FILE: content/collections/modifiers/is_numberwang.md ================================================ --- id: f3e9d043-ff25-4778-9915-0cfafc52e166 blueprint: modifiers modifier_types: - string - conditions title: 'Is Numberwang' --- Is it or is it not numberwang? Returns `true` if it is indeed numberwang. ```yaml number: 1002 ``` ::tabs ::tab antlers ```antlers {{ if number | is_numberwang }} ``` ::tab blade ```blade @if (Statamic::modify($number)->isNumberwang()->fetch()) ... @endif ``` :: ```html ??? ``` ================================================ FILE: content/collections/modifiers/is_numeric.md ================================================ --- id: 02db0ee5-585b-4e40-ab2b-f15a596b341c blueprint: modifiers modifier_types: - string - conditions title: 'Is Numeric' --- Returns `true` if variable is a number or numeric string. ```yaml sequence: 4815162342 another_sequence: just type 4 8 15 16 23 42 ``` ::tabs ::tab antlers ```antlers {{ if sequence | is_numeric }} {{ if another_sequence | is_numeric }} ``` ::tab blade ```blade @if (Statamic::modify($sequence)->isNumeric()->fetch()) ... @endif @if (Statamic::modify($another_sequence)->isNumeric()->fetch()) ... @endif ``` :: ```html true false ``` ================================================ FILE: content/collections/modifiers/is_past.md ================================================ --- id: fbf6eab4-0769-4e13-9205-f9f64fd44572 blueprint: modifiers modifier_types: - date - conditions title: 'Is Past' --- Returns `true` if date is in the past. ```yaml date: October 21 2015 another_date: November 2019 ``` ::tabs ::tab antlers ```antlers {{ if date | is_past }} {{ if another_date | is_past }} ``` ::tab blade ```blade @if (Statamic::modify($date)->isPast()->fetch()) ... @endif @if (Statamic::modify($another_date)->isPast()->fetch()) ... @endif ``` :: ```html true false ``` :::warning By default, when using a modifier on a date variable, it will be operating on the UTC date rather than the localized date. Please refer to our [Timezones](/tips/timezones) guide for more information. ::: ================================================ FILE: content/collections/modifiers/is_today.md ================================================ --- id: 50aa52bf-8c6c-4ec3-9af7-e610f65f8202 blueprint: modifiers modifier_types: - date - conditions title: 'Is Today' --- Returns `true` if a given date is today, using the server's time. ```yaml date: January 1, 2000 ``` ::tabs ::tab antlers ```antlers {{ if date | is_today }} ``` ::tab blade ```blade @if (Statamic::modify($date)->isToday()->fetch()) ... @endif ``` :: ```html false ``` :::warning By default, when using a modifier on a date variable, it will be operating on the UTC date rather than the localized date. Please refer to our [Timezones](/tips/timezones) guide for more information. ::: ================================================ FILE: content/collections/modifiers/is_tomorrow.md ================================================ --- id: 2e700683-1fb1-4cde-a019-67770ceabadf blueprint: modifiers title: 'Is Tomorrow' modifier_types: - date - conditions --- Returns `true` if a given date is tomorrow, using the server's time. ```yaml date: January 1, 2000 ``` ::tabs ::tab antlers ```antlers {{ if date | is_tomorrow }} ``` ::tab blade ```blade @if (Statamic::modify($date)->isTomorrow()->fetch()) ... @endif ``` :: ```html false ``` :::warning By default, when using a modifier on a date variable, it will be operating on the UTC date rather than the localized date. Please refer to our [Timezones](/tips/timezones) guide for more information. ::: ================================================ FILE: content/collections/modifiers/is_uppercase.md ================================================ --- id: d5635238-7d7a-4543-9afc-912bee6ad6fd blueprint: modifiers modifier_types: - string - conditions title: 'Is Uppercase' --- Returns `true` if string is only uppercase characters. ```yaml declaration: NOISES cite: anonymous ``` ::tabs ::tab antlers ```antlers {{ if declaration | is_uppercase }} {{ if cite | is_uppercase }} ``` ::tab blade ```blade @if (Statamic::modify($declaration)->isUppercase()->fetch()) ... @endif @if (Statamic::modify($cite)->isUppercase()->fetch()) ... @endif ``` :: ```html true false ``` ================================================ FILE: content/collections/modifiers/is_url.md ================================================ --- id: 85303071-213b-4e6c-8fb7-10f703a4a52e blueprint: modifiers modifier_types: - string - conditions title: 'Is Url' --- Returns `true` if a string is a valid URL. ```yaml a_url: http://google.com/ not_a_url: waffles ``` ::tabs ::tab antlers ```antlers {{ if a_url | is_url }} {{ if not_a_url | is_url }} ``` ::tab blade ```blade @if (Statamic::modify($a_url)->isUrl()->fetch()) ... @endif @if (Statamic::modify($not_a_url)->isUrl()->fetch()) ... @endif ``` :: ```html true false ``` ================================================ FILE: content/collections/modifiers/is_weekday.md ================================================ --- id: a190fa95-c405-4e2c-b3c0-adfbe21f9bb2 blueprint: modifiers modifier_types: - date - conditions title: 'Is Weekday' --- Returns `true` if date is a weekday. ```yaml date: December 25 2015 ``` ::tabs ::tab antlers ```antlers {{ if date | is_weekday }} ``` ::tab blade ```blade @if (Statamic::modify($date)->isWeekday()->fetch()) ... @endif ``` :: ```html true ``` :::warning By default, when using a modifier on a date variable, it will be operating on the UTC date rather than the localized date. Please refer to our [Timezones](/tips/timezones) guide for more information. ::: ================================================ FILE: content/collections/modifiers/is_weekend.md ================================================ --- id: 22a4460a-b24a-4e24-bd8c-655d03e6d3de blueprint: modifiers modifier_types: - date - conditions title: 'Is Weekend' --- Returns `true` if date is on the weekend. ```yaml date: December 25 2015 ``` ::tabs ::tab antlers ```antlers {{ if date | is_weekend }} ``` ::tab blade ```blade @if (Statamic::modify($date)->isWeekend()->fetch()) ... @endif ``` :: ```html false ``` :::warning By default, when using a modifier on a date variable, it will be operating on the UTC date rather than the localized date. Please refer to our [Timezones](/tips/timezones) guide for more information. ::: ================================================ FILE: content/collections/modifiers/is_yesterday.md ================================================ --- id: bd468407-617a-4cb8-93d8-cfd7148ec157 blueprint: modifiers modifier_types: - date - conditions title: 'Is Yesterday' --- Returns `true` if a given date is yesterday, using the server's time. ```yaml date: January 1, 2000 ``` ::tabs ::tab antlers ```antlers {{ if date | is_yesterday }} ``` ::tab blade ```blade @if (Statamic::modify($date)->isYesterday()->fetch()) ... @endif ``` :: ```html false ``` :::warning By default, when using a modifier on a date variable, it will be operating on the UTC date rather than the localized date. Please refer to our [Timezones](/tips/timezones) guide for more information. ::: ================================================ FILE: content/collections/modifiers/iso_format.md ================================================ --- id: f72ffc08-4294-4c1c-9085-2794ee57962d blueprint: modifiers modifier_types: - date - string title: 'Iso Format' --- Given a date string, or anything that even sorta kinda looks like a date string, will convert it to a [Carbon][carbon] instance and allow you to format it with ISO format. This allows you to use inner translations rather than language packages you need to install on every machine where you deploy your site. The language that will be used for translations depends on what you configured in your `config/statamic/sites.php` file. The `locale` and `fallback_locale` settings from the `config/app.php` file have **no influence** on this modifier. This is also compatible with [momentjs format method](https://momentjs.com/), it means you can use same format strings as you may have used in moment from your front-end or other node.js application. Check out the [complete list of available replacements](https://carbon.nesbot.com/guide/getting-started/localization.html#iso-format-available-replacements). ```yaml event_date: June 19 2020 ``` ::tabs ::tab antlers ```antlers {{ event_date | iso_format("MMMM Do YYYY, h:mm:ss a") }} ``` ::tab blade ```blade {{ Statamic::modify($event_date)->isoFormat(["MMMM Do YYYY, h:mm:ss a"]) }} ``` :: ```html June 15th 2018, 5:34:15 pm ``` You can use macro-formats to format and localize dates as well. ::tabs ::tab antlers ```antlers {{ event_date | iso_format('ll') }} ``` ::tab blade ```blade {{ Statamic::modify($event_date)->isoFormat('ll') }} ``` :: Will output this on your English site: ```html Jan 5, 2017 ``` And this on your French site: ```html 5 janv. 2017 ``` Check out the [complete list of available macro-formats](https://carbon.nesbot.com/guide/getting-started/localization.html#iso-format-available-replacements). :::warning By default, when using a modifier on a date variable, it will be operating on the UTC date rather than the localized date. Please refer to our [Timezones](/tips/timezones) guide for more information. ::: [carbon]: http://carbon.nesbot.com ================================================ FILE: content/collections/modifiers/join.md ================================================ --- id: 9dfc5020-3d14-4774-a1f6-d82d051cb964 blueprint: modifiers modifier_types: - string - array - utility title: Join --- Turn an array into a string by gluing together all the data with any specified delimiter. It uses a comma by default. ```yaml tasks: - take a shower - brush hair - clip toenails ``` ::tabs ::tab antlers ```antlers {{ tasks | join }} {{ tasks | join(" + ") }} = ready ``` ::tab blade ```blade {{ Statamic::modify($tasks)->join() }} {{ Statamic::modify($tasks)->join(' + ') }} = ready ``` :: ```html take a shower, brush hair, clip toenails take a shower + brush hair + clip toenails = ready ``` ================================================ FILE: content/collections/modifiers/kebab.md ================================================ --- id: eef6642f-053a-4720-a373-78b950d949f2 blueprint: modifiers modifier_types: - string - utility title: Kebab Case --- Converts a string into `kebab-case`. ```yaml string: statamicIsAwesome ``` ::tabs ::tab antlers ```antlers {{ string | kebab }} ``` ::tab blade ```blade {{ Statamic::modify($string)->kebab() }} ``` :: ```html statamic-is-awesome ``` ================================================ FILE: content/collections/modifiers/key_by.md ================================================ --- id: 66b59b24-e908-a7df-fe01-f10e79487d22 blueprint: modifiers title: 'Key By' modifier_types: - array --- Re-keys a numerically indexed array using the value of a given key on each item. Handy when you want to target specific items by name instead of looping, like the `fields` array inside a form tag. ```yaml fields: - handle: name display: 'Your Name' - handle: email display: 'Email Address' ``` ::tabs ::tab antlers ```antlers {{ rekeyed = fields | key_by('handle') }}
    {{ rekeyed:name:display }}
    ``` ::tab blade ```blade @php $rekeyed = Statamic::modify($fields)->keyBy('handle')->fetch(); @endphp
    {{ $rekeyed['name']['display'] }}
    ``` :: ```html
    Your Name
    ``` :::tip If two items share the same key, the last one wins. Pick a key that's unique across the array (like `handle` or `id`). ::: ================================================ FILE: content/collections/modifiers/keys.md ================================================ --- id: 9197be05-5e2d-400f-a0f0-c52a4d460e60 blueprint: modifiers title: Keys modifier_types: - array - utility --- Retrieves just the keys from the given array. ```yaml the_team: jack: Jack McDade jason: Jason Varga jesse: Jesse Leite joshua: Joshua Blum duncan: Duncan McClean ``` ::tabs ::tab antlers ```antlers {{ the_team | keys }} ``` ::tab blade ```blade keys()->fetch(); ?> ``` :: ```yaml - jack - jason - jesse - joshua - duncan ``` ================================================ FILE: content/collections/modifiers/last.md ================================================ --- id: 4444ed5b-b543-424b-b7cf-a1eeff0213f9 blueprint: modifiers modifier_types: - markup - string - utility - array title: Last --- Returns the last X characters of a string, where X is any positive integer, or the last item in an array. ```yaml title: 2015 Denver Nuggets array: - Sonic - Knuckles - Tails ``` ::tabs ::tab antlers ```antlers {{ title | last(7) }} {{ array | last }} ``` ::tab blade ```blade {{ Statamic::modify($title)->last(7) }} {{ Statamic::modify($array)->last() }} ``` :: ```html Nuggets Tails ``` ================================================ FILE: content/collections/modifiers/lcfirst.md ================================================ --- id: 638e875e-2cc8-4b7b-953a-4f1a44c76e4d blueprint: modifiers modifier_types: - string - utility title: Lcfirst --- Converts the first character of the supplied string to lower case. ```yaml title: Wow ``` ::tabs ::tab antlers ```antlers {{ title | lcfirst }} ``` ::tab blade ```blade {{ Statamic::modify($title)->lcfirst() }} ``` :: ```html wow ``` ================================================ FILE: content/collections/modifiers/length.md ================================================ --- id: 9002885e-20e9-4d1c-8396-1c8011076d2c blueprint: modifiers modifier_types: - array - string - utility title: Length --- Returns the number of items in an array or characters in a string. ```yaml array: - Taylor Swift - Left Shark - Leroy Jenkins string: LEEEEROOOYYYY JEEENKINNNSS! ``` ::tabs ::tab antlers ```antlers {{ array | length }} {{ string | length }} ``` ::tab blade ```blade {{ Statamic::modify($array)->length() }} {{ Statamic::modify($string)->length() }} ``` :: ```html 3 27 ``` ================================================ FILE: content/collections/modifiers/limit.md ================================================ --- id: a3c9e1c5-10ec-44da-b8d8-fdc603fce5a3 blueprint: modifiers modifier_types: - array - utility title: Limit --- Limits the number of items returned in an array. ```yaml playlist: - Emancipator - Gong Gong - Possom Posse - Justin Bieber ``` Use with the pipe syntax to continue chaining in a single tag like so: ::tabs ::tab antlers ```antlers {{ playlist | limit(2) | join }} ``` ::tab blade ```blade {{ Statamic::modify($playlist)->limit(2)->join() }} ``` :: ```html Emancipator, Gong Gong ``` Or using the parameter syntax: ::tabs ::tab antlers ```antlers {{ playlist | limit(2) }}
  • {{ value }}
  • {{ /playlist }} ``` ::tab blade ```blade limit(2); ?> @foreach ($limitedPlaylist as $item)
  • {{ $item }}
  • @endforeach ``` :: ```html
  • Emancipator
  • Gong Gong
  • ``` ================================================ FILE: content/collections/modifiers/link.md ================================================ --- id: 16312447-a597-4a98-9726-8e97718c9788 blueprint: modifiers modifier_types: - markup attributes: true title: Link --- Generate an HTML link element with the value as `href`. ```yaml neat_site: http://example.com ``` ::tabs ::tab antlers ```antlers {{ neat_site | link }} ``` ::tab blade ```blade {{ Statamic::modify($neat_site)->link() }} ``` :: ```html http://example.com ``` ================================================ FILE: content/collections/modifiers/list.md ================================================ --- id: d8a8568c-bb93-4e84-8d30-e527b3b02876 blueprint: modifiers modifier_types: - array - markup title: List related_entries: - 6866c25b-1266-4908-8325-dce4e5146f5b - eed4c5bc-0923-4f54-ad37-ca9a3384e1e0 - cbab1bb5-302e-499d-badb-f154dbae751d - 9dfc5020-3d14-4774-a1f6-d82d051cb964 --- Turn a simple array into a comma delimited list with no comma after the last item. ```yaml things: - batman - zombies - scrunchies ``` ::tabs ::tab antlers ```antlers {{ things | list }} ``` ::tab blade ```blade {{ Statamic::modify($things)->list() }} ``` :: ```html batman, zombies, scrunchies ``` ================================================ FILE: content/collections/modifiers/lower.md ================================================ --- id: 14ef311c-b49a-45af-a0aa-e80f68793ba8 blueprint: modifiers modifier_types: - string - utility title: Lower --- Converts all characters in the string to lowercase. ```yaml yelling: I DON'T KNOW WHAT WE'RE YELLING ABOUT ``` ::tabs ::tab antlers ```antlers {{ yelling | lower }} ``` ::tab blade ```blade {{ Statamic::modify($yelling)->lower() }} ``` :: ```html i don't know what we're yelling about ``` ================================================ FILE: content/collections/modifiers/macro.md ================================================ --- id: c0ab61af-c0c2-4ead-b64f-2e23325e917f blueprint: modifiers modifier_types: - string - array - utility - markup title: Macro --- Macro is a very special modifier. It performs no modifications of its own, but rather lets you create reusable groups of modifiers and give them a name. Those groups are each called a "macro" and are stored in your `resources/macros.yaml` file. Keep in mind that the order of modifiers within a macro matter, the same way as regular modifiers. ```yaml # /resources/macros.yaml headline: title: true widont: true remove_right: . # Page content title: Actually i don't know what we're talking about. ``` ::tabs ::tab antlers ```antlers {{ title | macro('headline') }} ``` ::tab blade ```blade {{ Statamic::modify($title)->macro('headline') }} ``` :: ```html Actually I Don't Know What We're Talking About ``` When passing multiple parameters to a modifier, you'll need to pop down into a simple list: ```yaml # /resources/macros.yaml excerpt: safe_truncate: - 175 - ... ``` This is equivalent to: ::tabs ::tab antlers ```antlers {{ content | safe_truncate(175, '...') }} ``` ::tab blade ```blade {{ Statamic::modify($content)->safeTruncate([175, '...']) }} ``` :: ================================================ FILE: content/collections/modifiers/mailto.md ================================================ --- id: 65bcc454-2731-4f83-97cf-03659fb38db5 blueprint: modifiers modifier_types: - markup attributes: true title: Mailto --- Generate a `mailto` link element with the value as the email address. If it's _not_ an email address, it's going to be one busted link. ```yaml holler: holler@example.com ``` ::tabs ::tab antlers ```antlers {{ holler | mailto }} ``` ::tab blade ```blade {{ Statamic::modify($holler)->mailto() }} ``` :: ```html holler@example.com ``` ================================================ FILE: content/collections/modifiers/mark.md ================================================ --- id: 390ae639-0964-4d51-b7ed-efd3b810913c blueprint: modifiers title: Mark modifier_types: - string - utility --- Wrap any matched words in `` tags to highlight them on the page. ```yaml description: This cat video is the okayest thing ever. ``` ::tabs ::tab antlers ```antlers {{ description | mark('cat thing') }} {{ description | mark('video', 'class:highlight') }} ``` ::tab blade ```blade {!! Statamic::modify($description)->mark('cat thing') !!} {!! Statamic::modify($description)->mark(['video', 'class:highlight']) !!} ``` :: ```html This cat video is the okayest thing ever. ``` ```html This cat video is the okayest thing ever. ``` If no words are specified the `get:q` value will be used by default. :::tip This modifier expects HTML input. While most plain text strings will work just fine you should escape the value with the `entities` modifier if your text contains less than or greater than symbols: `{{ plain_text | entities | mark }}` ::: ================================================ FILE: content/collections/modifiers/markdown.md ================================================ --- id: 39dcb2b1-a319-4a0b-b7d0-5c7a1b8aa31b blueprint: modifiers modifier_types: - markup attributes: true title: Markdown --- Transform a string with [Markdown][markdown]. ```yaml quote: You can't wait for inspiration. **You have to go after it with a club.** ``` ::tabs ::tab antlers ```antlers {{ quote | markdown }} ``` ::tab blade ```blade {!! Statamic::modify($quote)->markdown() !!} ``` :: ```html

    You can't wait for inspiration. You have to go after it with a club.

    ``` [markdown]: https://daringfireball.net/projects/markdown/ ================================================ FILE: content/collections/modifiers/md5.md ================================================ --- id: 3d293101-4bbf-45ab-9291-a37fa898d2de blueprint: modifiers modifier_types: - string title: Md5 --- Creates an md5 hash of a variable. ::tabs ::tab antlers ```antlers {{ "hello" | md5 }} ``` ::tab blade ```blade {{ Statamic::modify('hello')->md5() }} -- or -- {{ md5('hello') }} ``` :: ```html 5d41402abc4b2a76b9719d911017c592 ``` ================================================ FILE: content/collections/modifiers/merge.md ================================================ --- id: d15bda69-36ee-4871-82b2-e66447868643 blueprint: modifiers title: Merge --- Merge an array variable with another array variable. ```yaml good_ideas: - Exercise regularly - Brush your teeth - Use Oxford Commas bad_ideas: - Bath in beans - Wear sandpaper underwear - Eat turtle shells ``` In this template example we'll merge the two arrays and then pull out a single random item from the combined list. For fun! ::tabs ::tab antlers ```antlers

    Picking a random idea!

    {{ good_ideas | merge($bad_ideas) | sort("random") | limit(1) }}

    {{ value }}

    {{ /good_ideas }} ``` ::tab blade ```blade merge($bad_ideas) ->sort('random') ->limit(1) ->fetch(); ?>

    Picking a random idea!

    @foreach ($merged as $item)

    {{ $item }}

    @endforeach ``` :: ```

    Use Oxford Commas

    ``` ================================================ FILE: content/collections/modifiers/minutes_ago.md ================================================ --- id: 06027289-825e-4205-bd3a-f375e26ab81e blueprint: modifiers modifier_types: - date date: 'October 1 2015 8:30:am' title: 'Minutes Ago' related_entries: - e73f1574-732e-4a74-be47-37e1fddb05d6 - 603701ba-5da7-4ec8-abe5-5bc9fe6861ea - 7ba53a64-0266-4752-af5b-282a40dd11fa - 6fcbfa5c-854e-4541-9955-505eca0d6bf7 - 6ebb6c28-d1f3-4362-92a0-8a16b5c9cd51 - 811c1cf5-797f-4e77-af92-fde6c03e96d2 --- Returns the number of minutes since a given date variable. Statamic will attempt to parse any string as a date, but try to keep it in the least ambiguous date format possible. ```yaml date: October 1 2015 8:30:am ``` ::tabs ::tab antlers ```antlers {{ date | minutes_ago }} ``` ::tab blade ```blade {{ Statamic::modify($date)->minutesAgo() }} ``` :: ```html {{ test_date | minutes_ago }} ``` ================================================ FILE: content/collections/modifiers/mod.md ================================================ --- id: 10875a6a-9cb8-4e2a-9d39-0d8e4059d815 blueprint: modifiers modifier_types: - math title: Mod --- Get the modulus value (remainder after division) of a value split by another numeric value. Pass an integer or the name of a second variable as the parameter. Also supports `%` as shorthand. ```yaml bottles: 3 glasses: 14 ``` ::tabs ::tab antlers ```antlers {{ glasses | mod(14) }} {{ glasses | mod($bottles) }} {{ glasses | %($bottles) }} ``` ::tab blade ```blade {{ Statamic::modify($glasses)->mod(14) }} {{ Statamic::modify($glasses)->mod($bottles) }} ``` :: ```html 0 2 2 ``` ================================================ FILE: content/collections/modifiers/modify_date.md ================================================ --- id: 18596c62-5535-41a8-91c4-b5769fb11085 blueprint: modifiers modifier_types: - date title: 'Modify Date' --- Alters a timestamp by incrementing or decrementing in a format accepted by PHP's native [`strtotime()`](http://php.net/manual/en/function.strtotime.php) method. ```yaml date: January 1, 2000 ``` ::tabs ::tab antlers ```antlers {{ date | modify_date("-1 day") }} {{ date | modify_date("next Sunday") }} {{ date | modify_date("+3 months") }} ``` ::tab blade ```blade {{ Statamic::modify($date)->modifyDate('-1 day') }} {{ Statamic::modify($date)->modifyDate('next Sunday') }} {{ Statamic::modify($date)->modifyDate('+3 months') }} ``` :: ```html December 31, 1999 January 2, 2000 April 1, 2000 ``` :::tip As of Statamic 5, this modifier will return a copy of the Date. Earlier versions would **modify the variable directly** which will be passed onto any additional modifiers. ::: :::warning By default, when using a modifier on a date variable, it will be operating on the UTC date rather than the localized date. Please refer to our [Timezones](/tips/timezones) guide for more information. ::: ================================================ FILE: content/collections/modifiers/months_ago.md ================================================ --- id: 7ba53a64-0266-4752-af5b-282a40dd11fa blueprint: modifiers modifier_types: - date title: 'Months Ago' related_entries: - e73f1574-732e-4a74-be47-37e1fddb05d6 - 603701ba-5da7-4ec8-abe5-5bc9fe6861ea - 06027289-825e-4205-bd3a-f375e26ab81e - 6fcbfa5c-854e-4541-9955-505eca0d6bf7 - 811c1cf5-797f-4e77-af92-fde6c03e96d2 - 6ebb6c28-d1f3-4362-92a0-8a16b5c9cd51 --- Returns the number of months since a given date variable. Statamic will attempt to parse any string as a date, but try to keep it in the least ambiguous date format possible. ```yaml date: October 1 2017 ``` ::tabs ::tab antlers ```antlers {{ date | months_ago }} ``` ::tab blade ```blade {{ Statamic::modify($date)->monthsAgo() }} ``` :: ```html {{ test_date | months_ago }} ``` ================================================ FILE: content/collections/modifiers/multiply.md ================================================ --- id: b1241017-a321-42ad-b550-a49ae8b3a805 blueprint: modifiers modifier_types: - math title: Multiply --- Multiply a value or another variable to your variable. Pass an integer or the name of a second variable as the parameter. Also supports `*` as shorthand. ```yaml smiles: 3 winks: 4 ``` ::tabs ::tab antlers ```antlers {{ smiles | multiply(10) }} {{ smiles | multiply($winks) }} {{ smiles | *($winks) }} ``` ::tab blade ```blade {{ Statamic::modify($smiles)->multiply(10) }} {{ Statamic::modify($smiles)->multiply($winks) }} ``` :: ```html 30 12 12 ``` ================================================ FILE: content/collections/modifiers/nl2br.md ================================================ --- id: ecbf79c1-be5d-412e-a677-30a847cfffa6 blueprint: modifiers modifier_types: - string - markup attributes: true title: Nl2br --- Replaces line breaks with `
    ` tags. ```yaml summary: | This is a summary on multiple lines ``` ::tabs ::tab antlers ```antlers {{ summary | nl2br }} ``` ::tab blade ```blade {!! Statamic::modify($summary)->nl2br() !!} ``` :: ```html This is a summary on multiple lines ``` ================================================ FILE: content/collections/modifiers/obfuscate.md ================================================ --- id: 84aca464-65b1-4cc9-8bc9-e64b33797579 blueprint: modifiers modifier_types: - markup - utility attributes: true title: Obfuscate --- Obfuscates a string with special characters making it hard for spam bots to sniff out and scrape off your site. Still appears like the same string to the reader. This is usually used for email addresses. ```yaml magic_word: Abracadabra ``` ::tabs ::tab antlers ```antlers {{ magic_word | obfuscate }} ``` ::tab blade ```blade {{ Statamic::modify($magic_word)->obfuscate() }} ``` :: ```html # visibly appears as Abracadabra Abracadabra ``` ================================================ FILE: content/collections/modifiers/obfuscate_email.md ================================================ --- id: 536ac4b3-bfc7-4ced-8c83-d389fb94a262 blueprint: modifiers modifier_types: - markup attributes: true title: 'Obfuscate Email' --- Obfuscates an email address with special characters making it hard for spam bots to sniff out and scrape off your site. Still reads like an email address as far as readers are concerned. ```yaml holler: holler@example.com ``` ::tabs ::tab antlers ```antlers {{ holler | obfuscate_email }} ``` ::tab blade {{ Statamic::modify($holler)->obfuscateEmail() }} :: ```html # output appears as holler@example.com holler@example.com ``` ================================================ FILE: content/collections/modifiers/offset.md ================================================ --- id: 9433b8cd-b2e0-4fbf-85bd-85edf317efa4 blueprint: modifiers modifier_types: - array - utility title: Offset --- Offsets the items returned in an array. ```yaml playlist: - Emancipator - Gong Gong - Possom Posse - Justin Bieber ``` Use with the pipe syntax to continue chaining in a single tag like so: ::tabs ::tab antlers ```antlers {{ playlist | offset(1) | join }} ``` ::tab blade ```blade {{ Statamic::modify($playlist)->offset(1)->join() }} ``` :: ```html Gong Gong, Possom Posse, Justin Bieber ``` Or using the parameter syntax: ::tabs ::tab antlers ```antlers {{ playlist | offset(1) }}
  • {{ value }}
  • {{ /playlist }} ``` ::tab blade ```blade @foreach (Statamic::modify($playlist)->offset(1)->fetch() as $value)
  • {{ $valuie }}
  • @endforeach ``` :: ```html
  • Gong Gong
  • Possom Posse
  • Justin Bieber
  • ``` ================================================ FILE: content/collections/modifiers/ol.md ================================================ --- id: 327f4a3b-04d4-4069-881a-fe50ddb9be23 blueprint: modifiers modifier_types: - array - markup title: OL --- Turn an array into an HTML ordered list element. ```yaml food: - sushi - broccoli - kale ``` ::tabs ::tab antlers ```antlers {{ food | ol }} ``` ::tab blade ```blade {!! Statamic::modify($food)->ol() !!} ``` :: ```html
    1. sushi
    2. broccoli
    3. kale
    ``` ================================================ FILE: content/collections/modifiers/option_list.md ================================================ --- id: 6866c25b-1266-4908-8325-dce4e5146f5b blueprint: modifiers modifier_types: - array - utility title: 'Option List' related_entries: - d8a8568c-bb93-4e84-8d30-e527b3b02876 - eed4c5bc-0923-4f54-ad37-ca9a3384e1e0 - cbab1bb5-302e-499d-badb-f154dbae751d - 9dfc5020-3d14-4774-a1f6-d82d051cb964 --- Turn an array into a pipe-delimited string. Useful when passing an array of things into a parameter. Generally unnecessary though as most parameters can now accept a wide range of datatypes. ```yaml collections: - blog - news - wigs ``` ::tabs ::tab antlers ```antlers {{ collection from="{collections|option_list}" }} ``` ::tab blade ```blade ``` :: Can also be used by its alias, [`piped`](/modifiers/piped). ================================================ FILE: content/collections/modifiers/output.md ================================================ --- id: b5409a41-cc62-4ca1-bb8a-f6700b2c8c29 blueprint: modifiers title: Output modifier_types: - asset - utility --- Given the URL to an Asset file, returns the string output of an Asset file's contents. This is primarily useful for rendering inline SVGs, but could also be used to display a lot of gibberish to your users if you're into that kind of thing. ```yaml icon: /img/icons/heart.svg ``` ::tabs ::tab antlers ```antlers {{ icon | output }} ``` ::tab blade ```blade {!! Statamic::modify($icon)->output() !!} ``` :: ```html ``` ================================================ FILE: content/collections/modifiers/overlaps.md ================================================ --- id: d66ba89d-2451-497d-bd6d-37710e9de171 blueprint: modifiers modifier_types: - array - conditions title: Overlaps --- Check if any values in an array are found in another array. Returns `true` if at least one value matches, otherwise `false`. The first parameter is the "needle" to find in the "haystack". It will read from the context if there is a matching variable, otherwise it will use the parameter as the value. The needle can be a single value or an array. This is a loose comparison that mirrors how [`whereJsonOverlaps()`](https://laravel.com/docs/queries#json-where-clauses) works on the query builder side. ```yaml shopping_list: - eggs - flour - beef jerky want: - eggs - oatmeal ``` ::tabs ::tab antlers ```antlers {{ if shopping_list | overlaps('want') }} GOT SOMETHING! {{ /if }} {{ if shopping_list | overlaps('flour') }} GOT IT! {{ /if }} {{ if shopping_list | overlaps('kale') }} Not today. {{ /if }} ``` ::tab blade ```blade @if (Statamic::modify($shopping_list)->overlaps('want')->fetch()) GOT SOMETHING! @endif @if (Statamic::modify($shopping_list)->overlaps('flour')->fetch()) GOT IT! @endif @if (Statamic::modify($shopping_list)->overlaps('kale')->fetch()) Not today. @endif ``` :: ```html GOT SOMETHING! GOT IT! ``` ================================================ FILE: content/collections/modifiers/pad.md ================================================ --- id: 0a1595bc-5b41-401c-bb9c-45c35e8e5d7c blueprint: modifiers modifier_types: - array title: Pad --- Pad an array to a given number of items with a value. By default the value is null, but you can specify it as the second parameter. ```yaml epic_meal_time: - jack daniels - bacon strips ``` ::tabs ::tab antlers ```antlers {{ epic_meal_time | pad(4, "bacon strips") }} {{ value }} {{ /epic_meal_time }} ``` ::tab blade ```blade pad(4, 'bacon strips') ->fetch(); ?> @foreach ($meals as $meal) {{ $meal }} @endforeach ``` :: ```html jack daniels bacon strips bacon strips bacon strips ``` ================================================ FILE: content/collections/modifiers/parse_url.md ================================================ --- id: e1c41970-5894-4c23-a5af-08f4dd1901ed blueprint: modifiers title: 'Parse Url' modifier_types: - string - utility --- Get information about a URL. ``` yaml url: 'http://example.com/path?query=1' ``` ::tabs ::tab antlers ``` antlers {{ url | parse_url }} {{ url | parse_url('host') }} ``` ::tab blade ```blade {{ Statamic::modify($url)->parseUrl() }} {{ Statamic::modify($url)->parseUrl('host') }} ``` :: ```php [ 'scheme' => 'http', 'host' => 'example.com', 'path' => '/path', 'query' => 'query=1', ] ``` ``` example.com ``` ================================================ FILE: content/collections/modifiers/partial.md ================================================ --- id: 23d51738-5043-49e3-8ca3-d5848427216f blueprint: modifiers modifier_types: - utility - string - array title: Partial --- Inject a variable's data into a partial and render it without any page scopes whatsoever. This is really just syntactical sugar, but it _is_ delicious. ```yaml data: title: Bubble Guppies content: Science died a little bit today. ``` ```

    {{ title }}

    {{ content | markdown }} {{ data | partial('demo') }} ``` ```html

    Bubble Guppies

    Science died a little bit today.

    ``` ================================================ FILE: content/collections/modifiers/pathinfo.md ================================================ --- id: 1fb22c41-eb1e-4a14-98fc-9d3158871476 blueprint: modifiers title: Pathinfo modifier_types: - string - utility --- Get information about a file path. ``` yaml path: '/local/file/example.pdf' ``` ::tabs ::tab antlers ``` antlers {{ path | pathinfo }} {{ path | pathinfo('extension') }} ``` ::tab blade ```blade {{ Statamic::modify($path)->pathinfo() }} {{ Statamic::modify($path)->pathinfo('extension') }} ``` :: ```php [ 'dirname' => '/local/file', 'basename' => 'example.pdf', 'filename' => 'example', 'extension' => 'pdf', ] ``` ``` pdf ``` ================================================ FILE: content/collections/modifiers/piped.md ================================================ --- id: 124c1470-a9da-11e7-8f1a-0800200c9a66 blueprint: modifiers modifier_types: - array - utility title: Piped --- Alias of [option_list](/modifiers/option_list) ================================================ FILE: content/collections/modifiers/pluck.md ================================================ --- id: 17075d87-df8f-4ab7-b957-67cdae80ac0a blueprint: modifiers title: Pluck modifier_types: - array - utility --- Retrieves the values of a given `key` from each item. ```yaml games: - feeling: love title: Dominion - feeling: love title: Netrunner - feeling: hate title: Chutes and Ladders ``` ::tabs ::tab antlers ```antlers {{ games | pluck('title') }} ``` ::tab blade ```blade pluck('title') ->fetch(); ?> ``` :: ```yaml games: - Dominion - Netrunner - Chutes and Ladders ``` ================================================ FILE: content/collections/modifiers/plural.md ================================================ --- id: d464d979-7e57-40a6-8892-a08d08bd7ccf blueprint: modifiers modifier_types: - array - utility title: Plural --- Get the plural form of an English word. Accepts a numerical parameter, either as a literal value or a variable, to control plurality. It's important to note that you should use the singular form of the word to ensure the best results. ```yaml shopping_list: - item: pickle quantity: 1 - item: apple quantity: 12 - item: donut quantity: 500 ``` ::tabs ::tab antlers ```antlers Please pick up the following items: {{ shopping_list }} - {{ quantity }} {{ item | plural($quantity) }}. {{ /shopping_list }} ``` ::tab blade ```blade @foreach ($shopping_list as $item) - {{ $item['quantity'] }} {{ Statamic::modify($item)->plural($item['quantity']) }} @endforeach ``` :: ```html Please pick up the following items: - 1 pickle - 3 apples - 500 donuts ``` ================================================ FILE: content/collections/modifiers/random.md ================================================ --- id: 48178377-da99-4754-ae9e-d294720cff33 blueprint: modifiers modifier_types: - array - utility title: Random related_entries: - 63acdaa6-9724-4179-b210-ea5d507672e9 --- ## Overview Picks a _single_ random item from an array or collection. If you are trying to get _multiple_ random items, consider the [shuffle modifier](/modifiers/shuffle). ## Example ### The YAML ```yaml arr: - Soda - Pop - Coke ``` ### The Template ::tabs ::tab antlers ```antlers {{ arr | random }} ``` ::tab blade ```blade {{ Statamic::modify($arr)->random() }} ``` :: ### The Output ``` Soda (maybe) ``` ================================================ FILE: content/collections/modifiers/raw.md ================================================ --- id: e100a366-b69c-4d59-bec7-eac18c0b286b blueprint: modifiers modifier_types: - string - array - utility title: Raw --- ## Overview Returns the [unaugmented](/augmentation) version of the variable. ## Example If you had a Markdown field and wanted to render the actual Markdown-formatted text instead of rendered HTML, you can do this: ### The YAML ```yaml markdown_field: > # How to Breakdance First you do the fancy kicky thing with your feets, and then you flail your legs around like a battery operated fan at a hot summer ballgame. ``` ### The Template ::tabs ::tab antlers ```antlers {{ markdown_field | raw }} ``` ::tab blade ```blade {{ $markdown_field->raw() }} ``` :: ### The Output ``` # How to Breakdance First you do the fancy kicky thing with your feets, and then you flail your legs around like a battery operated fan at a hot summer ballgame. ``` ================================================ FILE: content/collections/modifiers/rawurlencode.md ================================================ --- id: a4bf0c06-9210-4200-881b-feb9011ea2f7 blueprint: modifiers modifier_types: - string - utility title: Raw URL Encode --- URL-encode a variable according to [RFC 3986][rfc-3986]. ```yaml example: please and thank you/Mommy ``` ::tabs ::tab antlers ```antlers http://example.com/{{ example | rawurlencode }} ``` ::tab blade ```blade https://example.com/{{ Statamic::modify($example)->rawurlencode() }} ``` :: ```html http://example.com/please%20and%20thank%20you%2FMommy ``` If you don't want forward slashes (`/`) to be encoded, use the [rawurlencode_except_slashes](/modifiers/rawurlencode_except_slashes) modifier instead. [rfc-3986]: http://php.net/manual/en/function.rawurlencode.php ================================================ FILE: content/collections/modifiers/rawurlencode_except_slashes.md ================================================ --- id: b3da88d2-7251-4827-aa88-e746647ee00b blueprint: modifiers modifier_types: - string - utility title: Raw URL Encode Except Slashes --- URL-encode a variable according to [RFC 3986][rfc-3986]. Just like [rawurlencode](/modifiers/rawurlencode), but doesn't encode forward slashes (`/`). ```yaml example: please and thank you/Mommy ``` ::tabs ::tab antlers ```antlers http://example.com/{{ example | rawurlencode_except_slashes }} ``` ::tab blade ```blade https://example.com/{{ Statamic::modify($example)->rawurlencode_except_slashes() }} ``` :: ```html http://example.com/please%20and%20thank%20you/Mommy ``` [rfc-3986]: http://php.net/manual/en/function.rawurlencode.php ================================================ FILE: content/collections/modifiers/ray.md ================================================ --- id: 1bb00dc7-f4b2-4bba-aaf9-45e3a2a19518 blueprint: modifiers modifier_types: - utility title: Ray related_entries: - 12de1a6c-e8be-4703-81a3-fc270311bc84 - 985dc29c-fe71-464e-bb83-4f3f2aa455c0 --- Send a variable to Spatie's [Ray](https://myray.app) app. You can pass a string with a color name as parameter to get it colored in Ray. Note that you need to have the [spatie/laravel-ray](https://github.com/spatie/laravel-ray) package installed. ::tabs ::tab antlers ```antlers {{ your_field | ray }} {{ your_field | ray('red'} } ``` ::tab blade ```blade @php(Statamic::modify($your_field)->ray()) @php(Statamic::modify($your_field)->ray('red')) -- or -- @php(ray($your_field)) @php(ray($your_field)->red()) ``` :: ================================================ FILE: content/collections/modifiers/read_time.md ================================================ --- id: 6a0abbe0-860a-4e30-b1e7-ecac343149ce blueprint: modifiers modifier_types: - string - utility title: 'Read Time' --- Provide an estimate of the read time in minutes based on a given number of words per minute. Defaults to 200/wpm. ```yaml --- title: A long post --- Pretend there are lots of words here... ``` ::tabs ::tab antlers ```antlers

    {{ title }}

    {{ content | read_time(180) }} min

    ``` ::tab blade ```blade

    {{ $title }}

    {{ Statamic::modify($content)->readTime(180) }} min

    ``` :: ```html

    A long post

    10 min

    ``` ================================================ FILE: content/collections/modifiers/regex_mark.md ================================================ --- id: 053e62e4-be01-4e82-944d-fe72b18a1a7c blueprint: modifiers title: 'Regex Mark' modifier_types: - string - utility --- Wrap any regex matches in `` tags to highlight them on the page. ```yaml description: This cat video is the okayest thing ever. ``` ::tabs ::tab antlers ```antlers {{ description | regex_mark('cat video|thing') }} {{ description | regex_mark('video', 'class:highlight') }} ``` ::tab blade ```blade {!! Statamic::modify($description)->regexMark('cat video|thing') !!} {!! Statamic::modify($description)->regexMark(['video', 'class:highlight']) !!} ``` :: ```html This cat video is the okayest thing ever. ``` ```html This cat video is the okayest thing ever. ``` :::tip This modifier expects HTML input. While most plain text strings will work just fine you should escape the value with the `entities` modifier if your text contains less than or greater than symbols: `{{ plain_text | entities | mark }}` ::: ================================================ FILE: content/collections/modifiers/regex_replace.md ================================================ --- id: 711d7eb2-8748-42a8-90c6-c91efb3ed818 blueprint: modifiers modifier_types: - string - utility title: 'Regex Replace' --- Run a find and replace regex on a string of content. ```yaml message: 'This is a great video: https://www.youtube.com/watch?v=YO_spdAYjPk' ``` ::tabs ::tab antlers ```antlers {{ message | regex_replace('watch\?v=[\w-]+', 'watch?v=dQw4w9WgXcQ') }} ``` ::tab blade ```blade {{ Statamic::modify($message)->regexReplace(['watch\?v=[\w-]+', 'watch?v=dQw4w9WgXcQ']) }} ``` :: ```html Check out this video: https://www.youtube.com/watch?v=eBGIQ7ZuuiU ``` Great for when your client keeps putting YouTube links in their content and you want to, uh, help them out. ================================================ FILE: content/collections/modifiers/relative.md ================================================ --- id: 40578328-3288-4c54-a475-8afad19a37e6 blueprint: modifiers modifier_types: - date title: Relative --- Returns a date difference in a nice, human readable, string format. This modifier will add a phrase after the difference value relative to the current date and the passed in date. You can turn off the extra words "ago", "until", and so on by passing `true` as a parameter The string will be localized into your current site locale. ```yaml past_date: October 1 2020 future_date: October 1 2024 ``` ::tabs ::tab antlers ```antlers {{ past_date | relative }} {{ past_date | relative(true) }} {{ future_date | relative }} {{ future_date | relative(true) }} ``` ::tab blade ```blade {{ Statamic::modify($past_date)->relative() }} {{ Statamic::modify($past_date)->relative(true) }} {{ Statamic::modify($future_date)->relative() }} {{ Statamic::modify($future_date)->relative(true) }} ``` :: ```html 2 years ago 2 years 1 year from now 1 year ``` :::warning By default, when using a modifier on a date variable, it will be operating on the UTC date rather than the localized date. Please refer to our [Timezones](/tips/timezones) guide for more information. ::: ================================================ FILE: content/collections/modifiers/remove_left.md ================================================ --- id: 71feed11-dcb7-4405-8f50-a17cb4c021ef blueprint: modifiers modifier_types: - string - utility title: 'Remove Left' --- Ensures that the string never begins with a specified string. ```yaml twitter: @statamic ``` ::tabs ::tab antlers ```antlers Twitter ``` ::tab blade ```blade Twitter ``` :: ```html Twitter ``` ================================================ FILE: content/collections/modifiers/remove_right.md ================================================ --- id: ec6e096f-ee52-449e-96b5-e0d759f982f0 blueprint: modifiers modifier_types: - string - utility title: 'Remove Right' --- Ensures that the string never ends with a specified string. ```yaml urls: - http://statamic.com/ - http://laravel.com/ ``` ::tabs ::tab antlers ```antlers {{ urls }} {{ value | remove_right('/') }} {{ /urls}} ``` ::tab blade ```blade @foreach ($urls as $url) {{ Statamic::modify($url)->removeRight('/') }} @endforeach ``` :: ```html http://statamic.com http://laravel.com ``` ================================================ FILE: content/collections/modifiers/repeat.md ================================================ --- id: 828a52f2-469e-4a20-82c1-15fd4d0e568c blueprint: modifiers modifier_types: - utility title: Repeat --- Repeats a value any given number of times. For fun. ```yaml lyric: can't touch this ``` ::tabs ::tab antlers ```antlers {{ lyric | repeat(3) }} ``` ::tab blade ```blade {{ Statamic::modify($lyric)->repeat(3) }} ``` :: ```html can't touch this can't touch this can't touch this ``` ================================================ FILE: content/collections/modifiers/replace.md ================================================ --- id: ab02d964-9a39-4f2b-ae87-d5248af9101e blueprint: modifiers modifier_types: - string - utility title: Replace --- Find and replace all occurrences of a string with a totally different string. ```yaml description: This cat video is the okayest thing ever. ``` ::tabs ::tab antlers ```antlers {{ description | replace('cat', 'dog') }} ``` ::tab blade {{ Statamic::modify($description)->replace(['cat', 'dog']) }} :: ```html This dog video is the okayest thing ever. ``` ================================================ FILE: content/collections/modifiers/resolve.md ================================================ --- id: de1fb94c-a0e5-43a9-8971-e9b839e373e8 blueprint: modifiers title: Resolve modifier_types: - array - utility - relationship --- Resolves a query builder and returns either a specific item by key/index or the full array of results. Also works on arrays and collections. Handy for grabbing a single item out of a relationship field (like the first asset from an asset field) without having to loop. ```yaml gallery: - hero.jpg - action-shot.jpg - outtake.jpg ``` ::tabs ::tab antlers ```antlers ``` ::tab blade ```blade ``` :: ```html ``` ## Without a key Omit the key to resolve the query builder into an array of all items. Useful when you want to pass the resolved data into another modifier. ::tabs ::tab antlers ```antlers {{ gallery | resolve | count }} ``` ::tab blade ```blade {{ Statamic::modify($gallery)->resolve()->count() }} ``` :: ```html 3 ``` ================================================ FILE: content/collections/modifiers/reverse.md ================================================ --- id: 5ee6e776-5361-4d87-9227-c0461e33853f blueprint: modifiers modifier_types: - array - string - utility title: Reverse --- Reverse the order of the characters in a string or the items in an array. ```yaml status: repaid order_of_ceremony: - photos - service - eat - party ``` ::tabs ::tab antlers ```antlers {{ status | reverse }} {{ order_of_ceremony | reverse | list }} ``` ::tab blade ```blade {{ Statamic::modify($status)->reverse() }} {{ Statamic::modify($status)->reverse()->list() }} ``` :: ```html diaper party, eat, service, photos ``` ================================================ FILE: content/collections/modifiers/round.md ================================================ --- id: 5166ff7e-2fad-414c-b232-f04108c08900 blueprint: modifiers modifier_types: - math - utility title: Round --- Rounds a number to a specified precision (number of digits after the decimal point). Defaults to `0`, or whole numbers. ```yaml pi: 3.14159265359 ``` ::tabs ::tab antlers ```antlers {{ pi | round }} {{ pi | round(2) }} ``` ::tab blade ```blade {{ Statamic::modify($pi)->round() }} {{ Statamic::modify($pi)->round(2) }} ``` :: ```html 3 3.14 ``` ================================================ FILE: content/collections/modifiers/safe_truncate.md ================================================ --- id: 1267d7b0-8a07-4103-9570-86327fb8e250 blueprint: modifiers modifier_types: - string title: 'Safe Truncate' --- Truncates the string to a given length (parameter 1), while ensuring that it does not split words. You can append a string with parameter 2, and if truncating occurs the string is further truncated so that it may be appended without exceeding the desired length. ```yaml advice: > So, here’s some advice I wish I woulda got when I was your age: Live every week like it’s Shark Week. ``` ::tabs ::tab antlers ```antlers {{ advice | safe_truncate(90, '...') }} ``` ::tab blade ```blade {{ Statamic::modify($advice)->safeTruncate([90, '...']) }} ``` :: ```html So, here’s some advice I wish I woulda got when I was your age: Live every week like... ``` ================================================ FILE: content/collections/modifiers/sanitize.md ================================================ --- id: 9b9a3494-ebb0-4e28-95d3-6d7986b0386e blueprint: modifiers modifier_types: - string - utility title: Sanitize --- Convert special characters to HTML entities with [htmlspecialchars][htmlspecialchars]. ```yaml example: NEAT ``` ::tabs ::tab antlers ```antlers {{ example | sanitize }} ``` ::tab blade ```blade {!! Statamic::modify($example)->sanitize() !!} ``` :: ```html <b>NEAT<b> ``` ## Double Encoding You can double encode HTML entities by passing `true` as an argument. This is useful for preserving JSON formatting. ::tabs ::tab antlers ```antlers {{ example | sanitize(true) }} ``` ::tab blade ```blade {!! Statamic::modify($example)->sanitize(true) !!} ``` :: [htmlspecialchars]: http://php.net/manual/en/function.htmlspecialchars.php ================================================ FILE: content/collections/modifiers/scope.md ================================================ --- id: 4944d222-cc5b-451c-8ff9-17688de7764c blueprint: modifiers title: Scope modifier_types: - array - utility --- Adds a named scope prefix to each item in an array or collection, letting you access variables with that prefix to avoid collisions with the parent context. This is the modifier equivalent of the [`scope` parameter](/tags/collection#scope) on the Collection tag, but usable on any array — like the results of a [`taxonomy`](/tags/taxonomy) tag, a [Replicator](/fieldtypes/replicator) field, or any custom array of entries. ```yaml title: My Favorite Posts posts: - title: Bear Hibernation Tips slug: bear-hibernation - title: Cactus Cuddles slug: cactus-cuddles ``` Without scoping, `{{ title }}` inside the loop falls back to the page's `title`. Prefix the variables with a scope to get exactly what you want. ::tabs ::tab antlers ```antlers {{ posts | scope('post') }} {{ post:title }} {{ /posts }} ``` ::tab blade ```blade @foreach (Statamic::modify($posts)->scope('post')->fetch() as $item) {{ $item['post']['title'] }} @endforeach ``` :: ```html Bear Hibernation Tips Cactus Cuddles ``` The original (unprefixed) variables are still available inside the loop — the scope is added alongside them, not as a replacement. ================================================ FILE: content/collections/modifiers/seconds_ago.md ================================================ --- id: 603701ba-5da7-4ec8-abe5-5bc9fe6861ea blueprint: modifiers modifier_types: - date title: 'Seconds Ago' related_entries: - e73f1574-732e-4a74-be47-37e1fddb05d6 - 06027289-825e-4205-bd3a-f375e26ab81e - 7ba53a64-0266-4752-af5b-282a40dd11fa - 6fcbfa5c-854e-4541-9955-505eca0d6bf7 - 6ebb6c28-d1f3-4362-92a0-8a16b5c9cd51 - 811c1cf5-797f-4e77-af92-fde6c03e96d2 --- Returns the number of seconds since a given date variable. Statamic will attempt to parse any string as a date, but try to keep it in the least ambiguous date format possible. ```yaml date: October 1 2015 8:30:am ``` ::tabs ::tab antlers ```antlers {{ date | seconds_ago }} ``` ::tab blade ```blade {{ Statamic::modify($date)->secondsAgo() }} ``` :: ```html {{ test_date | seconds_ago }} ``` ================================================ FILE: content/collections/modifiers/segment.md ================================================ --- id: 87cb26b4-3eb1-4bd7-8d80-913f1ba21932 blueprint: modifiers modifier_types: - string - utility title: Segment --- Returns a segment by number from any valid URL or URI. ```yaml example: /this/is/pretty/neat ``` ::tabs ::tab antlers ```antlers {{ example | segment(4) }} ``` ::tab blade ```blade {{ Statamic::modify($example)->segment(4) }} ``` :: ```html neat ``` ================================================ FILE: content/collections/modifiers/select.md ================================================ --- id: 864d101a-c0a2-42d8-a644-80440316de8b blueprint: modifiers title: Select modifier_types: - array - utility --- Retrieves the selected values of a given array. ```yaml games: - feeling: love title: Dominion publisher: Rio Grande Games - feeling: love title: Netrunner publisher: Wizards of the Coast - feeling: hate title: Chutes and Ladders publisher: Unknown ``` ::tabs ::tab antlers ```antlers {{ games | select('title', 'publisher') }} ``` ::tab blade ```blade {{ Statamic::modify($games)->select(['title', 'published'])->fetch() }} ``` :: ```yaml - title: Dominion publisher: Rio Grande Games - title: Netrunner publisher: Wizards of the Coast - title: Chutes and Ladders publisher: Unknown ``` ================================================ FILE: content/collections/modifiers/sentence_list.md ================================================ --- id: eed4c5bc-0923-4f54-ad37-ca9a3384e1e0 blueprint: modifiers modifier_types: - array - markup title: 'Sentence List' related_entries: - d8a8568c-bb93-4e84-8d30-e527b3b02876 - 6866c25b-1266-4908-8325-dce4e5146f5b - cbab1bb5-302e-499d-badb-f154dbae751d - 9dfc5020-3d14-4774-a1f6-d82d051cb964 --- Turn a simple array into a friendly comma delimited list with the word "and" before the last item. ```yaml things: - batman - zombies - scrunchies ``` ::tabs ::tab antlers ```antlers I like {{ things | sentence_list }}. ``` ::tab blade ```blade I like {{ Statamic::modify($things)->sentenceList() }}. ``` :: ```html I like batman, zombies, and scrunchies. ``` By default, the "glue" is the word "and", and will be translated appropriately. But, you can change it with the first argument: ::tabs ::tab antlers ```antlers I like {{ things | sentence_list('&') }}. ``` ::tab blade ```blade I like {{ Statamic::modify($things)->sentenceList('&') }}. ``` :: ```html I like batman, zombies, & scrunchies. ``` The second argument controls the oxford comma. Set that to 0 and it'll get removed: ::tabs ::tab antlers ```antlers I like {{ things | sentence_list('and', 0) }}. ``` ::tab blade ```blade I like {{ Statamic::modify($things)->sentenceList(['and', 0]) }}. ``` :: ```html I like batman, zombies and scrunchies. ``` ================================================ FILE: content/collections/modifiers/shuffle.md ================================================ --- id: 63acdaa6-9724-4179-b210-ea5d507672e9 blueprint: modifiers modifier_types: - array - markup - string title: Shuffle related_entries: - 48178377-da99-4754-ae9e-d294720cff33 --- Shuffles a string or an array to make it all random. ```yaml string: Mr. Roboto was the original hipster. array: - Sonic - Knuckles - Tails ``` ::tabs ::tab antlers ```antlers {{ string | shuffle }} {{ array | shuffle }} ``` ::tab blade ```blade {{ Statamic::modify($string)->shuffle() }} {{ Statamic::modify($array)->shuffle()->fetch() }} ``` :: ```yaml string: a nhglRsws.oMtiotr hprriao eeo.b ti array: - Tails - Knuckles - Sonic ``` ================================================ FILE: content/collections/modifiers/singular.md ================================================ --- id: ef2a1b64-d2a6-4ade-988d-33b279e47bfd blueprint: modifiers modifier_types: - string title: Singular --- Get the singular form of an English word. ```yaml word: nickles ``` ::tabs ::tab antlers ```antlers {{ word | singular }} ``` ::tab blade ```blade {{ Statamic::modify($word)->singular() }} ``` :: ```html nickle ``` ================================================ FILE: content/collections/modifiers/slugify.md ================================================ --- id: 15ab735c-a877-423a-8e7f-c61e3f68744b blueprint: modifiers modifier_types: - string - utility title: Slugify --- Converts the string into an URL slug. This includes replacing non-ASCII characters with their closest ASCII equivalents, removing remaining non-ASCII and non-alphanumeric characters, and replacing whitespace with dashes. And then everything is lowercased. ```yaml string: Please, have some lemoñade. ``` ::tabs ::tab antlers ```antlers {{ string | slugify }} ``` ::tab blade ```blade {{ Statamic::modify($string)->slugify() }} ``` :: ```html please-have-some-lemonade ``` ================================================ FILE: content/collections/modifiers/smartypants.md ================================================ --- id: 287d14d7-6660-4ac6-aa1b-0ca4d298cdcc blueprint: modifiers modifier_types: - string title: Smartypants --- Translate plain ASCII punctuation characters into “smart” typographic punctuation HTML entities. It performs the following transformations: - Straight quotes (`"` and `'`) into “curly” quote HTML entities - Backticks-style quotes (``like this\'\') into “curly” quote HTML entities - Two dashes (`--`) into an em dash. - Three consecutive dots (`...`) into an ellipsis entity ```yaml conversation: | "What's your favorite album?" asked Lars. ``...And Justice for All'' replied Kirk -- who was icing his hands after a 20 minute guitar solo. ``` ::tabs ::tab antlers ```antlers {{ conversation | smartypants }} ``` ::tab blade ```blade {{ Statamic::modify($conversation)->smartypants() }} ``` :: ```html “What’s your favorite album?” asked Lars. “…And Justice for All” replied Kirk — who was icing his hands after a 20 minute guitar solo. ``` or more precisely... ```html “What’s your favorite album?” asked Lars. “…And Justice for All” replied Kirk — who was icing his hands after a 20 minute guitar solo. ``` ================================================ FILE: content/collections/modifiers/snake.md ================================================ --- id: bc02aa00-6b1f-42f6-8cc3-737f803c5070 blueprint: modifiers modifier_types: - string - utility title: Snake Case --- Converts a string into `snake_case`. ```yaml string: statamicIsAwesome ``` ::tabs ::tab antlers ```antlers {{ string | snake }} ``` ::tab blade ```blade {{ Statamic::modify($string)->snake() }} ``` :: ```html statamic_is_awesome ``` ================================================ FILE: content/collections/modifiers/sort.md ================================================ --- id: 9e3bb06e-6f3f-460d-9693-c433452d0f96 blueprint: modifiers modifier_types: - array title: Sort --- Sort an array by key as parameter 1 and direction (`asc`/`desc`) as parameter 2. If sorting a primitive list no parameters are necessary. ```yaml primitive: - Zebra - Alpha - Bravo complex: - last_name: Zebra first_name: Zealous - last_name: Alpha first_name: Altruistic - last_name: Bravo first_name: Blathering ``` ``` {{ primitive | sort | list }} {{ complex | sort('last_name') }} Hello, {{ first_name }} {{ last_name }} {{ /complex }} {{ complex | sort('last_name', 'desc') }} Hello, {{ first_name }} {{ last_name }} {{ /complex }} ``` ```html Alpha, Bravo, Zebra Hello, Altruistic Alpha Hello, Blathering Bravo Hello, Zealous Zebra Hello, Zealous Zebra Hello, Blathering Bravo Hello, Altruistic Alpha ``` ================================================ FILE: content/collections/modifiers/spaceless.md ================================================ --- id: 3bcdbffa-8020-4364-8c2a-1fe1a9ff0a5c blueprint: modifiers modifier_types: - string - utility added_in: 2.8.4 title: Spaceless --- Removes excess whitespace and line breaks from a string. A definite OCD pleaser. ``` html: |

    I copy & pasted this link for you!

    ``` ::tabs ::tab antlers ```antlers {{ html | spaceless }} ``` ::tab blade ```blade {!! Statamic::modify($html)->spaceless() !!} ``` :: ```html

    I copy & pasted this link for you!

    ``` ================================================ FILE: content/collections/modifiers/split.md ================================================ --- id: f1f9e882-e9e1-4161-8d6e-13fa9838dde1 blueprint: modifiers modifier_types: - array - markup - utility title: Split --- Break an array or collection into a given number of (roughly equal) groups. :::tip Need a fixed number of items _per group_ instead of a fixed group count? The [chunk](/modifiers/chunk) modifier does the opposite — give it a _size_ and it makes as many groups as needed. ::: For example, `split:3` on a 6-item array produces 3 groups of 2. Each group is available as `{{ items }}` in Antlers (or `$group['items']` in Blade). ::tabs ::tab antlers ```antlers {{ collection:news as="posts" limit="6" }} {{ posts split="3" }}
    {{ items }} {{ title }} {{ /items }}
    {{ /posts }} {{ /collection:news }} ``` ::tab blade ```blade @foreach (Statamic::modify($posts)->split(3) as $group)
    @foreach ($group['items'] as $entry) {{ $entry->title }} @endforeach
    @endforeach
    ``` :: ```html ``` ================================================ FILE: content/collections/modifiers/starts_with.md ================================================ --- id: 1b1581b6-859f-4559-850b-398b7437929a blueprint: modifiers modifier_types: - conditions title: 'Starts With' --- Returns `true` if the value starts with a given string. This comparison is case-insensitive. ```yaml reply: Actually, I disagree because this is the internet. ``` ::tabs ::tab antlers ```antlers {{ if reply | starts_with('actually') }} {{ if reply | starts_with('respectfully') }} ``` ::tab blade ```blade @if (Statamic::modify($reply)->startsWith('actually')->fetch()) @endif @if (Statamic::modify($reply)->startsWith('respectfully')->fetch()) @endif ``` :: ```html true false ``` ================================================ FILE: content/collections/modifiers/str_pad_left.md ================================================ --- id: d01d449b-42fa-48d5-b0be-4884ea587fe4 blueprint: modifiers modifier_types: - string title: 'String Pad Left' --- This modifier returns the string padded on the left to the specified padding length (paramameter 1) with a character of choice (parameter 2). ::tabs ::tab antlers ```antlers {{ 1 | str_pad_left(2,0) }} ``` ::tab blade ```blade {{ Statamic::modify(1)->strPadLeft([2, 0]) }} ``` :: ```html 01 ``` ================================================ FILE: content/collections/modifiers/strip_tags.md ================================================ --- id: f3538ff6-b658-45d0-b3e0-fbac49f05da9 blueprint: modifiers modifier_types: - markup - string - utility title: 'Strip Tags' --- Strip HTML tags from a string, allowing you optionally to pass in a list of tags or a variable name containing the specific tags you want stripped. ```yaml html: >

    "Things we lose have a way of coming back to us in the end, if not always in the way we expect."

    unwanted: [p, blockquote] ``` ::tabs ::tab antlers ```antlers {{ html | strip_tags }} {{ html | strip_tags('p') }} {{ html | strip_tags($unwanted) }} ``` ::tab blade ```blade {!! Statamic::modify($html)->stripTags() !!} {!! Statamic::modify($html)->stripTags('p') !!} {!! Statamic::modify($html)->stripTags($unwanted) !!} ``` :: ```html "Things we lose have a way of coming back to us in the end, if not always in the way we expect."
    "Things we lose have a way of coming back to us in the end, if not always in the way we expect."
    "Things we lose have a way of coming back to us in the end, if not always in the way we expect." ``` ================================================ FILE: content/collections/modifiers/studly.md ================================================ --- id: 5a1d121e-0401-49e2-8460-842717d01047 blueprint: modifiers modifier_types: - string - utility title: Studly Case --- Converts a string into `StudlyCase`. ```yaml string: statamic_is_awesome ``` ::tabs ::tab antlers ```antlers {{ string | studly }} ``` ::tab blade ```blade {{ Statamic::modify($string)->studly() }} ``` :: ```html StatamicIsAwesome ``` ================================================ FILE: content/collections/modifiers/substr.md ================================================ --- id: 963d5e43-7bf5-4669-93af-d1990f7f3c97 blueprint: modifiers modifier_types: - string - utility title: Substr --- Returns the string beginning at a given position with an optional length. If length not specific, will return the rest of the string. ```yaml string: How neat is that? ``` ::tabs ::tab antlers ```antlers {{ string | substr(0, 3) }} {{ string | substr(4, 4) }} {{ string | substr(-8, 8) }} ``` ::tab blade ```blade {{ Statamic::modify($string)->substr([0, 3]) }} {{ Statamic::modify($string)->substr([4, 4]) }} {{ Statamic::modify($string)->substr([-8, 8]) }} ``` :: ```html How neat is that? ``` ================================================ FILE: content/collections/modifiers/subtract.md ================================================ --- id: baad70cf-0af2-48bc-b102-b0da0293baf4 blueprint: modifiers modifier_types: - math title: Subtract --- Subtract a value or another variable to your variable. Pass an integer or the name of a second variable as the parameter. Also supports `-` as shorthand. ```yaml capacity: 2500 reservations: 1900 ``` ::tabs ::tab antlers ```antlers {{ capacity | subtract(1900) }} {{ capacity | subtract($reservations) }} {{ capacity | -($reservations) }} ``` ::tab blade ```blade {{ Statamic::modify($capacity)->subtract(1900) }} {{ Statamic::modify($capacity)->subtract($reservations) }} ``` :: ```html 600 600 600 ``` ================================================ FILE: content/collections/modifiers/sum.md ================================================ --- id: ee2da74a-0788-400f-804f-c85ad9b635c0 blueprint: modifiers modifier_types: - array - math title: Sum --- Returns the sum of all items in an array, optionally specified by a specific key. ```yaml numbers: - 5 - 10 - 20 - 40 stats: - player: Luke Skywalker score: 750 - player: Wedge Antilles score: 688 - player: Jar Jar Binks score: 1425 ``` ::tabs ::tab antlers ```antlers {{ numbers | sum }} {{ stats | sum('score') }} ``` ::tab blade ```blade {{ Statamic::modify($numbers)->sum() }} {{ Statamic::modify($numbers)->sum('score') }} ``` :: ```html 75 2863 ``` ================================================ FILE: content/collections/modifiers/surround.md ================================================ --- id: 1fea97b2-c42f-495b-846a-6c688d3b5eca blueprint: modifiers modifier_types: - string title: Surround --- Surrounds a string with another string. ```yaml string: ͜ ``` ::tabs ::tab antlers ```antlers {{ string | surround('ʘ') }} ``` ::tab blade ```blade {{ Statamic::modify($string)->surround('ʘ') }} ``` :: ```html ʘ ͜ ʘ ``` ================================================ FILE: content/collections/modifiers/swap_case.md ================================================ --- id: 5c1714c1-83fe-4690-8607-60d1f269408b blueprint: modifiers modifier_types: - string - utility title: 'Swap Case' --- Returns a case swapped version of the string. ```yaml string: IpHONE ``` ::tabs ::tab antlers ```antlers {{ string | swap_case }} ``` ::tab blade ```blade {{ Statamic::modify($string)->swapCase() }} ``` :: ```html iPhone ``` ================================================ FILE: content/collections/modifiers/table.md ================================================ --- id: 4d8e0392-af96-4b46-a73a-d3a47b57ccbe blueprint: modifiers title: Table modifier_types: - string - markup - utility --- Takes an array generated by the [Table Fieldtype](/fieldtypes/table), and converts into a basic HTML ``. ```yaml my_table: - cells: - One - Two - cells: - Three - Four ``` ::tabs ::tab antlers ```antlers {{ my_table | table }} ``` ::tab blade ```blade {!! Statamic::modify($my_table)->table() !!} ``` :: ```html
    One Two
    Three Four
    ``` You can pass `true` as an argument to parse the cell data as markdown. ``` {{ my_table | table(true) }} ``` ================================================ FILE: content/collections/modifiers/tidy.md ================================================ --- id: be5541eb-4e33-4699-8035-61ce09de3247 blueprint: modifiers modifier_types: - string - utility title: Tidy --- Returns a string with smart quotes, ellipsis characters, and dashes from Windows-1252 (commonly used in Word documents) replaced by their ASCII equivalents. ```yaml string: > “I see…” ``` ::tabs ::tab antlers ```antlers {{ string | tidy }} ``` ::tab blade ```blade {{ Statamic::modify($string)->tidy() }} ``` :: ```html "I see..." ``` ================================================ FILE: content/collections/modifiers/timezone.md ================================================ --- id: c5a7e328-3d6a-4866-970f-5a4e0061d606 blueprint: modifiers modifier_types: - date title: Timezone --- Applies a timezone to a date value. Aliased as `tz`. You may pass a [PHP timezone value](http://php.net/manual/en/timezones.php) to specify a timezone. ```yaml when: 2015-01-27 11:00 ``` ::tabs ::tab antlers ```antlers {{ when | format('r') }} {{ when | timezone('Australia/Sydney') | format('r') }} ``` ::tab blade ```blade {{ Statamic::modify($when)->format('r') }} {{ Statamic::modify($when)->timezone('Australia/Sydney')->format('r') }} ``` :: ```html Tue, 27 Jan 2015 11:00:00 -0500 Wed, 28 Jan 2015 03:00:00 +1100 ``` Using no parameter will simply use the `display_timezone` configured in your `config/statamic/system.php` config file. This is useful if your date value already contains a timezone, and you want to output it in the display timezone. ```yaml when: Tue, 27 Jan 2015 16:00:00 +0000 # Date in UTC ``` ::tabs ::tab antlers ```antlers {{ when | timezone | format('r') }} ``` ::tab blade ```blade {{ Statamic::modify($when)->timezone()->format('r') }} ``` :: ```html Tue, 27 Jan 2015 11:00:00 -0500 ``` ================================================ FILE: content/collections/modifiers/title.md ================================================ --- id: 2293d024-32ad-4bb6-a7ff-46ec2e1d9f2f blueprint: modifiers modifier_types: - string title: Title --- Returns a trimmed string with the first letter of each word capitalized, ignoring articles, coordinating conjunctions, and short propositions: `a`, `an`, `the`, `at`, `by`, `for`, `in`, `of`, `on`, `to`, `up`, `and`, `as`, `but`, `or`, and `nor`. ```yaml string: It was one of the best adventures of my life ``` ::tabs ::tab antlers ```antlers {{ string | title }} ``` ::tab blade ```blade {{ Statamic::modify($string)->title() }} ``` :: ```html It Was One of the Best Adventures of My Life ``` ================================================ FILE: content/collections/modifiers/to_json.md ================================================ --- id: c3214196-3d0d-4a3d-b6c3-1ee4960cfe5d blueprint: modifiers modifier_types: - utility title: 'To Json' --- Converts any variable into JSON. ```yaml stats: - player: Luke Skywalker score: 750 - player: Wedge Antilles score: 688 - player: Jar Jar Binks score: 1425 ``` ::tabs ::tab antlers ```antlers {{ stats | to_json }} ``` ::tab blade ```blade {!! Statamic::modify($stats)->toJson() !!} ``` :: ```html [ {"player":"Luke Skywalker","score":750}, {"player":"Wedge Antilles","score":688}, {"player":"Jar Jar Binks","score":1425} ] ``` ================================================ FILE: content/collections/modifiers/to_qs.md ================================================ --- id: 6ff17a73-c631-433b-9727-0f72fa543807 blueprint: modifiers title: 'To qs' modifier_types: - array - utility --- Converts an array or array-like value into a query string using Laravel's [Arr::query()](https://laravel.com/docs/13.x/helpers#method-array-query) helper method. ```yaml $params = [ 'mode' => 'plaid', 'area' => [51, 52], 'hat' => null, 'transportation' => [ 'bike' => true, 'delorian' => false, ], ]; ``` ```antlers Search Now ``` ```html Search Now ``` ================================================ FILE: content/collections/modifiers/to_spaces.md ================================================ --- id: d5ee1b1e-0ffa-45cd-b3aa-bfecb9a93325 blueprint: modifiers modifier_types: - utility title: 'To Spaces' --- Converts all tabs in a string to a given number of spaces, `4` by default. This is a boring modifier to output examples of. Here's just a few examples on how the syntax looks. ::tabs ::tab antlers ```antlers {{ string | to_spaces }} {{ string | to_spaces(2) }} ``` ::tab blade ```blade {{ Statamic::modify($string)->toSpaces() }} {{ Statamic::modify($string)->toSpaces(2) }} ``` :: ================================================ FILE: content/collections/modifiers/to_tabs.md ================================================ --- id: e13ecc17-1458-42f5-a55c-f7fcbac743ad blueprint: modifiers modifier_types: - utility title: 'To Tabs' --- Converts all instances of a specified number of spaces in a string to tabs. `4` by default. This is a boring modifier to output examples of. Here's just a few examples on how the syntax looks. ::tabs ::tab antlers ```antlers {{ string | to_tabs }} {{ string | to_tabs(4) }} ``` ::tab blade ```blade {{ Statamic::modify($string)->toTabs() }} {{ Statamic::modify($string)->toTabs(4) }} ``` :: ================================================ FILE: content/collections/modifiers/trans.md ================================================ --- id: c77b02b6-3ce5-40e0-964c-a669685c12d3 blueprint: modifiers title: Trans --- Retrieve a string from a language file in the current locale. It is the equivalent of the [trans and trans_choice methods](https://laravel.com/docs/localization) provided by Laravel. :::tip There's also a [tag](/tags/trans) version that you may prefer. ::: ## Usage {#usage} Get the `bar` string from the `lang/en/foo.php` translation file (where `en` is the current locale). ```php 'Bar!', 'welcome' => 'Welcome, :name!', 'apples' => 'There is one apple|There are :count apples', ]; ``` ``` yaml key: 'foo.bar' this_many: 2 ``` ::tabs ::tab antlers ```antlers {{ key | trans }} or {{ "foo.bar" | trans }} ``` ::tab blade ```blade {{ trans($key) }} {{ trans('foo.bar') }} ``` :: ```html Bar! ``` ## Replacements Parameter replacements are only supported in the [tag version](/tags/trans). ## Pluralization {#pluralization} To pluralize, use the `trans_choice` modifier with the count as the parameter. You can use a number or a variable. ::tabs ::tab antlers ```antlers {{ "foo.apples" | trans_choice(2) }} {{ "foo.apples" | trans_choice($this_many) }} ``` ::tab blade ```blade {{ trans_choice('foo.apples', 2) }} {{ trans_choice('foo.apples', $this_many) }} ``` :: ```html There are 2 apples ``` ## Strings As you can see above, you may use the modifier on inline strings. Instead of translation keys, you can use simple strings. These will be referenced from `lang/fr.json` (where `fr` is the locale). ``` json { "Hello": "Bonjour" } ``` ::tabs ::tab antlers ```antlers {{ "Hello" | trans }} ``` ::tab blade ```blade {{ trans('Hello') }} ``` :: ```html Bonjour ``` ================================================ FILE: content/collections/modifiers/trim.md ================================================ --- id: 64e41d8f-fedb-4639-bb09-d4e4cbfe3555 blueprint: modifiers modifier_types: - string title: Trim --- Returns a string with whitespace removed from the start and end of the string. Supports the removal of unicode whitespace. ```yaml string: " This is so sloppy " ``` ::tabs ::tab antlers ```antlers {{ string | trim }} ``` ::tab blade ```blade {{ Statamic::modify($string)->trim() }} ``` :: ```html This is so sloppy ``` ================================================ FILE: content/collections/modifiers/truncate.md ================================================ --- id: cc80cc58-f73a-47fd-8f4d-e1cfc23c5d56 blueprint: modifiers modifier_types: - string title: Truncate --- Truncates the string to a given length (parameter 1). You can append a string with parameter 2, and if truncating occurs the string is further truncated so that it may be appended without exceeding the desired length. This differs from [safe_truncate][safe_truncate] in that it _may_ truncate in the middle of a word. ```yaml advice: > So, here’s some advice I wish I woulda got when I was your age: Live every week like it’s Shark Week. ``` ::tabs ::tab antlers ```antlers {{ advice | truncate(90, '...') }} ``` ::tab blade ```blade {{ Statamic::modify($advice)->truncate([90, '...']) }} ``` :: ```html So, here’s some advice I wish I woulda got when I was your age: Live every week like i... ``` [safe_truncate]: /modifiers/safe_truncate ================================================ FILE: content/collections/modifiers/ucfirst.md ================================================ --- id: d977361d-0576-469d-9430-f4d82b5666b4 blueprint: modifiers modifier_types: - string title: Ucfirst --- Converts the first character of a string to upper case. ```yaml string: i wanna go home. ``` ::tabs ::tab antlers ```antlers {{ string | ucfirst }} ``` ::tab blade ```blade {{ Statamic::modify($string)->ucfirst() }} ``` :: ```html I wanna go home. ``` ================================================ FILE: content/collections/modifiers/ul.md ================================================ --- id: 85910466-876b-4fc7-9dd1-c9baa7f7870a blueprint: modifiers modifier_types: - array - markup title: UL --- Turn an array into an HTML unordered list element. ```yaml food: - sushi - broccoli - kale ``` ::tabs ::tab antlers ```antlers {{ food | ul }} ``` ::tab blade ```blade {!! Statamic::modify($food)->ul() !!} ``` :: ```html
    • sushi
    • broccoli
    • kale
    ``` ================================================ FILE: content/collections/modifiers/underscored.md ================================================ --- id: 61a30026-8f98-454e-bcf2-fa7ec1435438 blueprint: modifiers modifier_types: - string - utility title: Underscored --- Returns a lowercase and trimmed string separated by underscores. Underscores are inserted before uppercase characters (with the exception of the first character of the string), and in place of spaces as well as dashes. ```yaml string: Please and thank you ``` ::tabs ::tab antlers ```antlers {{ string | underscored }} ``` ::tab blade ```blade {{ Statamic::modify($string)->underscored() }} ``` :: ```html please_and_thank_you ``` ================================================ FILE: content/collections/modifiers/unique.md ================================================ --- id: f1486fa5-7cce-4c75-90cd-e131f5f6d184 blueprint: modifiers modifier_types: - array - utility title: Unique --- Returns all of the unique items in the array: ```yaml checklist: - zebra - hippo - hyena - giraffe - zebra - hippo - hippo - hippo - hippo ``` ::tabs ::tab antlers ```antlers {{ checklist | unique | list }} ``` ::tab blade ```blade {{ Statamic::modify($checklist)->unique()->list() }} ``` :: ```html zebra, hippo, hyena, giraffe ``` ================================================ FILE: content/collections/modifiers/upper.md ================================================ --- id: 33b1003c-6ce8-47db-a4ec-bbc323e15820 blueprint: modifiers modifier_types: - string title: Upper --- Transform a string into uppercase. Multi-byte friendly. ```yaml string: That is über neat. ``` ::tabs ::tab antlers ```antlers {{ string | upper }} ``` ::tab blade ```blade {{ Statamic::modify($string)->upper() }} ``` :: ```html THAT IS ÜBER NEAT. ``` ================================================ FILE: content/collections/modifiers/url.md ================================================ --- id: eb68e4e1-c1b2-4806-a477-6c0491616b88 blueprint: modifiers modifier_types: - asset - string - relationship title: Url --- Get the URL of an Asset, Page, Entry, or Taxonomy term from an ID. ```yaml hero_image: 98hf98-sfq4h8f94-fd9s0fj0l ``` ::tabs ::tab antlers ```antlers {{ hero_image | url }} ``` ::tab blade ```blade {{ Statamic::modify($hero_image)->url() }} ``` :: ```html /assets/flying-bacon-wearing-a-batman-mask.jpg ``` :::tip If your field is defined in a [Blueprint](/blueprints), Statamic would have already used [augmentation](/augmentation) to convert the ID to an object. You can access the URL like so: ``` {{ hero_image:url }} ``` ::: ================================================ FILE: content/collections/modifiers/urldecode.md ================================================ --- id: bf5018c9-3c15-4f93-927d-0ad3f728ac50 blueprint: modifiers modifier_types: - string - utility title: URL Decode --- URL-decodes a string. The inverse of [urlencode](/modifiers/urlencode) ```yaml string: I+just+want+%26+need+%24pecial+characters%21 ``` ::tabs ::tab antlers ```antlers {{ string | urldecode }} ``` ::tab blade ```blade {!! Statamic::modify($string)->urldecode() !!} ``` :: ```html I just want & need $pecial characters! ``` ================================================ FILE: content/collections/modifiers/urlencode.md ================================================ --- id: b27e8b53-f9bd-471d-8f36-17d51ec11a32 blueprint: modifiers modifier_types: - string - utility title: URL Encode --- URL-encodes a string. The inverse of [urldecode](/modifiers/urldecode). ```yaml string: I just want & need $pecial characters! ``` ::tabs ::tab antlers ```antlers {{ string | urlencode }} ``` ::tab blade ```blade {!! Statamic::modify($string)->urlencode() !!} ``` :: ```html I+just+want+%26+need+%24pecial+characters%21 ``` If you don't want forward slashes (`/`) to be encoded, use the [urlencode_except_slashes](/modifiers/urlencode_except_slashes) modifier instead. ================================================ FILE: content/collections/modifiers/urlencode_except_slashes.md ================================================ --- id: 81ba1f6a-0eaf-441b-a26d-1aeb81f135a0 blueprint: modifiers modifier_types: - string - utility title: URL Encode Except Slashes --- URL-encodes a string. Just like [urlencode](/modifiers/urldecode), but doesn't encode forward slashes (`/`). ```yaml string: please and thank you/Mommy ``` ::tabs ::tab antlers ```antlers {{ string | urlencode_except_slashes }} ``` ::tab blade ```blade {!! Statamic::modify($string)->urlencode_except_slashes() !!} ``` :: ```html please+and+thank+you/Mommy ``` ================================================ FILE: content/collections/modifiers/values.md ================================================ --- id: b57caa5c-ae9e-4220-b8a6-f7c82d16f0df blueprint: modifiers title: Values modifier_types: - array - utility --- Retrieves just the values from the given array. ```yaml the_team: jack: Jack McDade jason: Jason Varga jesse: Jesse Leite joshua: Joshua Blum duncan: Duncan McClean ``` ::tabs ::tab antlers ```antlers {{ the_team | values }} ``` ::tab blade ```blade {{ Statamic::modify($the_team)->values()->fetch() }} ``` :: ```yaml - Jack McDade - Jason Varga - Jesse Leite - Joshua Blum - Duncan McClean ``` ================================================ FILE: content/collections/modifiers/weeks_ago.md ================================================ --- id: 6fcbfa5c-854e-4541-9955-505eca0d6bf7 blueprint: modifiers modifier_types: - date title: 'Weeks Ago' related_entries: - e73f1574-732e-4a74-be47-37e1fddb05d6 - 603701ba-5da7-4ec8-abe5-5bc9fe6861ea - 7ba53a64-0266-4752-af5b-282a40dd11fa - 06027289-825e-4205-bd3a-f375e26ab81e - 6ebb6c28-d1f3-4362-92a0-8a16b5c9cd51 - 811c1cf5-797f-4e77-af92-fde6c03e96d2 --- Returns the number of weeks since a given date variable. Statamic will attempt to parse any string as a date, but try to keep it in the least ambiguous date format possible. ```yaml date: October 1 2015 ``` ::tabs ::tab antlers ```antlers {{ date | weeks_ago }} ``` ::tab blade ```blade {{ Statamic::modify($date)->weeksAgo() }} ``` :: ```html {{ test_date | weeks_ago }} ``` ================================================ FILE: content/collections/modifiers/where-in.md ================================================ --- id: c1141498-eff1-4fab-b24b-4d942784a748 blueprint: modifiers modifier_types: - conditions - array title: 'Where In' --- Filter an array (such as a Replicator field's data) to items where a `key` matches specific `values`. ```yaml games: - feeling: love title: Dominion - feeling: happy title: Netrunner - feeling: hate title: Chutes and Ladders ``` ::tabs ::tab antlers ```antlers

    I love...

    {{ games | where_in('feeling', ['love', 'happy']) }} {{ title }}
    {{ /games }} ``` ::tab blade ```blade whereIn(['feeling', ['love', 'happy']]) ->fetch(); ?>

    I love...

    @foreach ($filteredGames as $game) {{ $game['title'] }} @endforeach ``` :: ```html Dominion Netrunner ``` ================================================ FILE: content/collections/modifiers/where.md ================================================ --- id: c36a6f62-aaf4-478b-a469-29cdb1eab8dc blueprint: modifiers modifier_types: - conditions - array title: Where --- Filter an array (such as a Replicator field's data) to items where a `key` has a specific `value`. ```yaml games: - feeling: love title: Dominion - feeling: love title: Netrunner - feeling: hate title: Chutes and Ladders ``` ::tabs ::tab antlers ```antlers

    I love...

    {{ games | where('feeling', 'love') }} {{ title }}
    {{ /games }} ``` ::tab blade ```blade where(['feeling', 'love']) ->fetch(); ?>

    I love...

    @foreach ($filteredGames as $game) {{ $game['title'] }} @endforeach ``` :: ```html Dominion Netrunner ``` You can also pass an operator to the modifier, so you can do checks like "where not" and "where greater than". Under the hood, this uses [the `where` method of Laravel Collections](https://laravel.com/docs/13.x/collections#method-where), so you can use any operators it supports. ```

    I hate...

    {{ games | where('feeling', '!=', 'love') }} {{ title }}
    {{ /games }} ``` ================================================ FILE: content/collections/modifiers/widont.md ================================================ --- id: cbb290f3-ffd0-4dc1-8989-1d10a92ff17d blueprint: modifiers modifier_types: - string - utility title: Widont --- Attempts to prevent widows (a line with a single word) in a string by adding non-breaking spaces between the last two words of each paragraph. The first parameter allows you to customize the number of words to add non-breaking spaces to. ```yaml string: I Just Want Pretty Headlines and Sentences ``` ::tabs ::tab antlers ```antlers {{ string | widont }} {{ string | widont(4) }} ``` ::tab blade ```blade {{ Statamic::modify($string)->widont() }} {{ Statamic::modify($string)->widont(4) }} ``` :: ```html I Just Want Pretty Headlines and Sentences I Just Want Pretty Headlines and Sentences ``` ================================================ FILE: content/collections/modifiers/word_count.md ================================================ --- id: a9b3b597-9075-4320-bb6d-721f78c2de78 blueprint: modifiers modifier_types: - string - utility title: 'Word Count' --- Returns the number of words in a given string. ```yaml string: There are probably seven words in this sentence. ``` ::tabs ::tab antlers ```antlers {{ string | word_count }} ``` ::tab blade ```blade {{ Statamic::modify($string)->wordCount() }} ``` :: ```html 8 ``` ================================================ FILE: content/collections/modifiers/wrap.md ================================================ --- id: ee9e1c05-8b5d-47f9-b476-3d108a9c14af blueprint: modifiers modifier_types: - markup - string title: Wrap --- Wraps a string with a given HTML tag. Has the nice benefit of returning null if there is no data, eliminating the need for simple `{{ if }}` wrappers. ```yaml title: As the World Turns ``` ::tabs ::tab antlers ```antlers {{ title | wrap('h1') }} ``` ::tab blade ```blade {!! Statamic::modify($title)->wrap('h1') !!} ``` :: ```html

    As the World Turns

    ``` You may also use Emmet-style CSS classes to be added to the tag. ::tabs ::tab antlers ```antlers {{ title | wrap('h1.fast.furious') }} ``` ::tab blade ```blade {!! Statamic::modify($title)->wrap('h1.fast.furious') !!} ``` :: ```html

    As the World Turns

    ``` The `wrap` modifier also accepts passing in arrays. ```yaml team_members: - Jack - Jason - Jesse - Josh - Duncan - The Hoff ``` ::tabs ::tab antlers ```antlers {{ team_members | wrap('li') | join(' ') }} ``` ::tab blade ```blade {!! Statamic::modify($team_members)->wrap('li')->join(' ') !!} ``` :: ```html
  • Jack
  • Jason
  • Jesse
  • Josh
  • Duncan
  • The Hoff
  • ``` ================================================ FILE: content/collections/modifiers/years_ago.md ================================================ --- id: e73f1574-732e-4a74-be47-37e1fddb05d6 blueprint: modifiers modifier_types: - date title: 'Years Ago' related_entries: - 603701ba-5da7-4ec8-abe5-5bc9fe6861ea - 06027289-825e-4205-bd3a-f375e26ab81e - 7ba53a64-0266-4752-af5b-282a40dd11fa - 6fcbfa5c-854e-4541-9955-505eca0d6bf7 - 6ebb6c28-d1f3-4362-92a0-8a16b5c9cd51 - 811c1cf5-797f-4e77-af92-fde6c03e96d2 --- Returns the number of years since a given date variable. Statamic will attempt to parse any string as a date, but try to keep it in the least ambiguous date format possible. ```yaml date: October 1 2015 ``` ::tabs ::tab antlers ```antlers {{ date | years_ago }} ``` ::tab blade ```blade {{ Statamic::modify($date)->yearsAgo() }} ``` :: ```html {{ test_date | years_ago }} ``` ================================================ FILE: content/collections/modifiers.yaml ================================================ title: Modifiers icon: return-square template: page layout: layout mount: ccb11ef2-eef3-4c56-9052-e55905cffd1a taxonomies: - modifier_types revisions: false route: '/modifiers/{slug}' sort_dir: asc date_behavior: past: null future: null preview_targets: - label: Entry url: '{permalink}' refresh: true inject: view_model: App\ViewModels\Modifiers ================================================ FILE: content/collections/pages/3-0-to-3-1.md ================================================ --- id: 769f1c97-3fb4-4303-a60b-4096c06b7870 blueprint: page title: 'Upgrade from 3.0 to 3.1' intro: 'A guide for upgrading from 3.0 to 3.1' template: page updated_by: 3a60f79d-8381-4def-a970-5df62f0f5d56 updated_at: 1630938227 --- ## Overview First read through this guide to see if there's anything that you might need to adjust. When upgrading, Statamic may automate some things for you. They'll be noted below. In your `composer.json`, change the `statamic/cms` requirement: ```json "statamic/cms": "3.1.*" ``` Then run: ``` shell composer update statamic/cms --with-dependencies ``` ## Changes ### High impact changes - [Update Scripts](#update-scripts) - [Collection and Nav trees](#collection-and-nav-trees) - [Opt-in REST API Resources](#optin-rest-api-resources) ### Low impact changes - [REST API Filtering drafts](#rest-api-filtering-drafts) - [Entry author permissions](#entry-author-permissions) - [Date fieldtype augmentation](#date-fieldtype-augmentation) - [Static Caching interface](#static-caching-interface) - [Broadcasting auth endpoint](#broadcasting-auth-endpoint) ## Update scripts Statamic 3.1 introduced the concept of update scripts, which you can think of like database migrations. It'll let Statamic perform adjustments for any particular upgrade path. Some of the changes listed in this upgrade guide will be performed automatically by an update script. In order for it to work, you'll need a section added to your `composer.json`. **When upgrading using Composer, Statamic should add this automatically for you.** (An update script adding its own update script, say whaaat?) If it doesn't, add this `pre-update-cmd` under the `scripts` section of your `composer.json`: ```json "scripts": { "pre-update-cmd": [ "Statamic\\Console\\Composer\\Scripts::preUpdateCmd" ], ... } ``` ## Collection and Nav Trees The trees for collections and navs are now stored separately from the collections and navs themselves. **When upgrading using Composer, Statamic should update these automatically for you.** If for whatever reason it doesnt, follow these steps: If you've only got a single site: - The `tree` array found in each `content/collections/[handle].yaml` should be moved into a `content/trees/collections/[handle].yaml` file. - The `tree` array found in each `content/navigation/[handle].yaml` should be moved into a `content/trees/navigation/[handle].yaml` file. If you've got multiple sites: - In each `content/collections/[handle].yaml` with a `tree`, you should move each site's nested tree into a `content/trees/collections/[site]/[handle].yaml` file. - Each `content/navigation/[site]/[handle.yaml]` should be moved to `content/trees/navigation/[site]/[handle].yaml` file. Basically, the `trees` should be moved to their own dedicated directory. ## Opt-in REST API Resources All API endpoints have been made opt-in to improve security. This means that everything will 404 until you opt into the ones you need. Add the following array to `config/statamic/api.php` and set the resources you want to `true`. ```php 'resources' => [ 'collections' => false, 'navs' => false, 'taxonomies' => false, 'assets' => false, 'globals' => false, 'forms' => false, 'users' => false, ], ``` ## REST API Filtering drafts The API will now filter out draft entries. This is more of a bug fix than a breaking change. However, if you were relying on seeing draft entries, you should be aware they won't be there now. If you want to see both, you can use the status filter: ``` /api/endpoint?filter[status:in]=published|draft ``` ## Entry author permissions 3.1 introduces the ability to control how much you can control entries belonging to other users. This feature only has any effect if your entry blueprint has an `author` field. If you don't already have an `author` field, no functionality changes for you. If you _do_ have an `author` field, the permission logic will be enabled. **When upgrading using Composer, Statamic should automatically apply the following updates for you.** If for whatever reason it doesn't, you'll need to add the corresponding permissions in order to continue editing, deleting, and managing the publish state of other users' entries: - `edit other authors blog entries` (where `blog` is the collection's handle) - `publish other authors blog entries` - `delete other authors blog entries` e.g. If you had an `edit blog entries`, you should add `edit other authors blog entries`. ## Date fieldtype augmentation The `date` fieldtype now augments to `Carbon` instances. If you use `{{ a_date_field }}` in Antlers without any modifiers, they will now be output using the `date_format` configured in `config/statamic/system.php` (e.g. `January 1st, 2020`). Actual entry dates (i.e. the `{{ date }}` field) would have been formatted using the configured date format this way already. No change necessary. If you were using a modifier (e.g. `format`), there will also be no change. For other arbitrary date fields, the raw value (e.g. `2020-01-02`) would have been output. If you _want_ to continue to output that, you can explicitly use a modifier. e.g. `{{ a_date_field format="Y-m-d" }}` ## Static caching interface A `hasCachedPage` method has been added to the `Statamic\StaticCaching\Cacher` interface. If you're making a custom Static Caching class, you'll need to add this method. However, you're more than likely just extending `AbstractCacher`. In that case, no action is needed. ## Broadcasting auth endpoint This only affects you if you use broadcasting *and* you've dedicided to use custom routing instead of using Laravel's `Broadcast::routes()`. To tell Statamic about your custom auth endpoint, you should define it in `config/broadcasting.php`: ```php 'auth_endpoint' => '/your-endpoint' ``` ================================================ FILE: content/collections/pages/3-1-to-3-2.md ================================================ --- id: db244b81-13ad-42f1-b593-533c6e165ee6 blueprint: page title: 'Upgrade from 3.1 to 3.2' intro: 'A guide for upgrading from 3.1 to 3.2' template: page updated_by: 3a60f79d-8381-4def-a970-5df62f0f5d56 updated_at: 1630938184 --- ## Overview First read through this guide to see if there's anything that you might need to adjust. When upgrading, Statamic may automate some things for you. They'll be noted below. In your `composer.json`, change the `statamic/cms` requirement: ```json "statamic/cms": "3.2.*" ``` Then run: ``` shell composer update statamic/cms --with-dependencies ``` ## Changes ### Medium impact changes - [Nav Page IDs](#nav-page-ids) ### Low impact changes - [Nav GraphQL Changes](#nav-graphql-changes) - [Getting tree pages by ID](#getting-tree-pages-by-id) - [Tree root is now an array](#tree-root-is-now-an-array) ## Nav Page IDs In a **Navigation**, each branch now has its own automatically generated ID. (This doesn't apply to collection trees.) In 3.1: ``` yaml - url: /some-manually-added-link - entry: some-entry-id ``` In 3.2: ``` yaml - id: abc-def url: /some-manually-added-link - id: ghi-jkl entry: some-entry-id ``` In 3.1, if you were using a `nav` tag, the `{{ id }}` variable would be the ID of the entry for entry branches and `null` for non-entry branches. In 3.2, the `{{ id }}` will be the ID of the branch. To get the ID of the entry, you can use `{{ entry_id }}`. If you had this in 3.1: ``` {{ nav:links }} ... {{ id }} ... {{ /nav:links }} ``` Change to this for 3.2: ``` {{ nav:links }} ... {{ entry_id }} ... {{ /nav:links }} ``` :::tip If you're using the `nav` tag to output a _collection's_ tree (e.g. your `pages` collection), the `id` will still be the entry ID. ::: In PHP land, if you were doing `$page->id()`, you can now do `$page->reference()` or `optional($page->entry())->id()`. ## Nav GraphQL Changes ### TreeBranch type split The `TreeBranch` type has been split into `NavTreeBranch` and `CollectionTreeBranch` types. Likely the only place you would use this is if you were using the [Recursive Tree Branches example](/graphql#recursive-tree-branches): ```graphql fragment Fields on TreeBranch { # ... } fragment RecursiveChildren on TreeBranch { # ... } ``` ### PageInterface The `PageInterface` now only applies to navs. #### Usage in navs The `PageInterface` no longer contains all the entry's fields. It now has it's own subset of fields (`id`, `title`, `url`, `permalink`). If you needed entry specific fields, you can explicitly query the interface. In this example, `url` is included, but not `edit_url` anymore. Before: ```graphql page { url edit_url } ``` After: ```graphql page { url ... on EntryInterface { edit_url } } ``` If you added any [custom fields](/graphql#custom-fields) to `EntryInterface`, it will no longer be added to `PageInterface`. If you need it there, you can explicitly add it to `PageInterface`. #### Usage in collections Within a collection's tree, the `page` is now an `EntryInterface`. If you're using `EntryPage_x` implementations, you should now just use `Entry_x` implementations: ```graphql page { ... on EntryPage_Blog_Post { # change to Entry_Blog_post # ... } } ``` Also, within collection trees, since you're now getting the actual entry, `page` has been deprecated in favor of `entry`. Both still work but you may want to update it while you're here. ```graphql page { # change to entry # ... } ``` ## Getting tree pages by ID `$tree->page($id)` would previously get a page in a tree by an entry ID. Now, `$tree->page()` has been deprecated in favor of `$tree->find()` which will get a page by its ID, not the entry's ID. (For collection trees, since `entry` _is_ the ID, it'll work the same way.) If you want to find a page by the entry ID, there's a new `findByEntry($id)` method. ## Tree root is now an array `$tree->root()` would previous return the `entry` of the root tree branch. Now, it'll be the entire array. If you need the entry, you can do `$tree->root()['entry']`. ================================================ FILE: content/collections/pages/3-2-to-3-3.md ================================================ --- id: 4947cda2-d17d-4289-ab37-1f4f64bfa1d4 blueprint: page title: 'Upgrade from 3.2 to 3.3' intro: 'A guide for upgrading from 3.2 to 3.3. For most sites, the process will take less than 5 minutes. If your site is running an old version of PHP or Laravel, it may take a bit longer.' template: page --- ## Overview First read through this guide to see if there's anything that you might need to adjust. When upgrading, Statamic may automate some things for you. They'll be noted below. In your `composer.json`, change the `statamic/cms` requirement: ```json "statamic/cms": "3.3.*" ``` Then run: ``` shell composer update statamic/cms --with-dependencies ``` ## High impact changes ### PHP >=7.4 required {#php-version} Statamic 3.3 now requires at least PHP 7.4. If you're running a lower version, we recommend upgrading all the way to 8.1. ### Laravel 8 required Statamic 3.3 now requires at least Laravel 8. If you're running a lower version, we recommend upgrading all the way to 9. You can check your version of Laravel by running `php artisan -V`. Find out [how to upgrade from Laravel 7 to Laravel 8](/upgrade-guide/laravel-7-to-8). ## Medium impact changes ### Entries fieldtype augments to query builders {#entries-fieldtype} The `entries` fieldtype would previously augment to an `EntryCollection` of `Entry` objects. In 3.3, they will augment to a query builder. #### In Antlers If you were adding modifiers to your loop, you'll need to apply them to an inner aliased loop. Before: ``` {{ related_posts modifier="param" }} ... {{ /related_posts }} ``` After: ``` {{ related_posts as="whatever" }} {{ whatever modifier="param" }} ... {{ /whatever }} {{ /related_posts }} ``` #### In PHP If you're using them in PHP, you'll need to add a `->get()` to grab the entries from the query before continuing. Before: ```php $relatedPosts->someCollectionMethod(...); ``` After: ```php $relatedPosts->get()->someCollectionMethod(...); ``` ## Low impact changes ### New Experimental Antlers Parser {#antlers} 3.3 includes an overhauled Antlers parser. It fixes many issues with the existing parser and brings many new features. By upgrading to 3.3 **you will not automatically get the new parser**. If you'd like to use the new parser, [read about it in the docs](/new-antlers-parser), where it explains the experimental nature and how to use it. ### Date field's time_required setting has been removed {#date-time-required} The `time_required` option has been removed from the `date` fieldtype. The `time_enabled` setting still exists, and we suggest you use that. ### v-calendar upgraded to v2 {#v-calendar} If you were relying on the [v-calendar](https://github.com/nathanreyes/v-calendar) package within the Control Panel, be aware that it has been upgraded to v2. ### Property access on items now performs augmentation See PR [#5297](https://github.com/statamic/cms/issues/5297) for more details. Previously if you were to do `$entry->something`, it would get the raw value on the entry. It would _not_ factor in fallbacks from any origin entries, or the collection's injected data. It would _not_ do any augmentation. It would _not_ give you method-based values you might expect automatically in templates (like `id`, `slug`, etc). In 3.3, doing `$entry->something` will do all of those. It will factor in fallbacks, augment, and give you method based values. ```yaml id: 123 intro: 'hello' # (a "text" fieldtype) foo: 'bar' # (not even in the blueprint at all) content: | # (a "markdown" fieldtype) # Heading Paragraph ``` ```php // 3.2 $entry->content; // "# Heading\nParagraph" $entry->intro; // "hello" $entry->foo; // "bar" $entry->id; // null $entry->slug; // null $entry->url; // null // 3.3 $entry->content; // "

    Heading

    Paragraph

    $entry->intro; // "hello" $entry->foo; // "bar" $entry->id; // 123 $entry->slug; // "my-entry" $entry->url; // "/blog/my-entry" ``` On 3.2, property access on terms did nothing. You'd get a warning and null. ### Augmentation methods return Value instances {#augmentation-value-instances} See PR [#5302](https://github.com/statamic/cms/issues/5302) for more details. This change will only affect custom PHP code. This is considered a low impact change since it should only affect edge cases. - If you were explicitly coding something expecting a non-`Value`, it will now be a `Value`. - If it was being cast to a string or array, no change is needed since the `Value` class knows how to cast itself. For example, previously `toAugmentedArray()` you'd only get `Value` objects for fields that exist in the blueprint. ```php $arr = $entry->toAugmentedArray(); // [ // 'published' => false, // 'url' => '/blog/my-post' // 'some_blueprint_field' => Value('some value'), // ... // ] $inBlog = (Str::startsWith($arr['url'], '/blog')) ? 'yes' : 'no'; // yes $isPublished = ($arr['published']) ? 'yes' : 'no'; // 'no' ``` In 3.3, everything in the array would be wrapped in `Value` objects. ```php $arr = $entry->toAugmentedArray(); // [ // 'published' => Value(false), // 'url' => Value('/my-post') // 'some_blueprint_field' => Value('some value'), // ... // ] // ✅ No change. It would cast the Value to a string, giving you the same outcome. $inBlog = (Str::startsWith($arr['url'], '/blog')) ? 'yes' : 'no'; // yes // 🚨 This is changing. // Previously it would check "if true/false". But now it's "if object" which will always be true. $isPublished = ($arr['published']) ? 'yes' : 'no'; // 'yes' ``` You'll now need to add `->value()` to get the underlying value. ```php $isPublished = ($arr['published']->value()) ? 'yes' : 'no'; // 'yes' ``` The same goes for other augmentation methods: - `$entry->toAugmentedArray()` will only contain `Value` instances. - `$entry->toAugmentedCollection()` will only contain `Value` instances. - `$entry->augmentedValue('field')` will always return a `Value` instance. - `$entry->augmented()->get('field')` will always return a `Value` instance. But really, the fix would be to avoid the manual augmentation methods entirely and just do `$entry->fieldname`, `$entry->published`, etc. ```php $isPublished = ($entry->published) ? 'yes' : 'no'; // 'yes' ``` ### Form submission data is an unfiltered collection In 3.2, if you did `$submission->data()` you would sometimes get an array, sometimes an `Illuminate\Support\Collection` (the inconsistency was a bug) and any keys that didn't exist in the blueprint would get filtered out. In 3.3, you will always get a Collection, and it will not have any filtering applied. ### Live Preview If you're not overriding the `toLivePreviewResponse` in the `Entry` or `Term` classes, there is no change for you. The `toLivePreviewResponse` methods have been removed in favor of a token based system. You can instead migrate to a dedicated route that will get the Live Preview version of the entry/term via a token. See the [Live Preview Custom Rendering docs](/live-preview#custom-rendering) for how to do this. ### Custom date fields are now Carbon instances Custom date fields are now stored in the Stache as `Carbon` instances. This will only affect you if you're performing a query on a `date` field expecting it to be a string. For instance, `$query->where('datefield', 'like', '2020-%')` or `:datefield:starts_with="2020"` The actual `date` field on an entry (i.e. the publish date) would have already been a Carbon instance. This only applies to `date` fields that aren't named `date`. ### Grids, Replicators, and Bards augment differently Previously, these fields all augment to arrays containing arrays for each row or set. In 3.3, it will be an array of `Values` instances representing each row or set. In your templates there will be no changes. There's only a change necessary if you are writing PHP and expecting those to be arrays. You can get the underlying array by doing `$row->all()`. ### Commonmark 2 is supported Laravel 8 and Statamic 3.3 now support both CommonMark 1 or 2. When you do a `composer update`, Composer will try to install the latest available version, which may be v2. If you have any [custom Markdown extensions](/extending/markdown#customizing-markdown-behavior) you will need to either: - [upgrade them for Commonmark 2](https://commonmark.thephpleague.com/2.0/upgrading/developers/) - **or** you may require `league/commonmark ^1.6` in your project. If you don't have any custom extensions, the switch from Commonmark v1 to v2 should make no difference to you. ### Control panel forms now only submit visible fields Control Panel forms now only submit visible fields (as originally intended) which fixes `sometimes` / `required_if` / etc. validation rules, among other things. This could potentially be a breaking change if you were using field conditions purely for cosmetic showing/hiding of form fields, in which case we might recommend using the [revealer fieldtype](/fieldtypes/revealer). Read more: [Conditional Fields Data Flow](/conditional-fields#data-flow) ## Zero impact changes These are items that you can completely ignore. They are suggestions on how to improve your code using newly added features. ### You probably don't need to manually augment If you were manually grabbing an augmented value instance, then getting the actual augmented value from that - you can now just use the magic getters to get the underlying augmented value. ```php $entry->augmentedValue('fieldname')->value(); // [tl! --] $entry->fieldname; // [tl! ++] ``` ### Leverage relationship magic methods If you were manually performing a new query based on selections from an entries fieldtype, you can now use the magic method to get a query builder. ```php $relatedIds = $entry->get('related_posts'); // [tl! --] $selectedFeaturedEntries = Entry::query()->whereIn('id', $relatedIds)->where('featured', true)->get(); // [tl! --] $selectedFeaturedEntries = $entry->related_posts()->where('featured', true)->get(); // [tl! ++] ``` ### Use Tags in Blade If you were using the [Blade Directives](https://github.com/edalzell/statamic-blade) addon to work with Statamic data in your Blade templates, you can now use a whole bunch of native features instead. ```blade @collection('pages', ['title:is' => 'My Title', 'author:is' => 'Erin', 'limit' => 3, 'sort' => 'title:desc']) {{-- [tl! --] --}} {{-- [tl! ++:start] --}} @foreach (Statamic::tag('collection:pages') ->params(['title:is' => 'My Title', 'author:is' => 'Erin']) ->limit(3)->sort('title:desc') as $entry ) {{-- [tl! ++:end] --}} @if($entry['no_results']) {{-- [tl! --] --}} @if($entry->no_results) {{-- [tl! ++] --}}

    There are no results

    @else {{ $entry['title'] }} {{-- [tl! --] --}} {{ $entry->title }} {{-- [tl! ++] --}} @endif @endcollection ``` ```php return [ 'providers' => [ Edalzell\Blade\Augmentation\AugmentationViewServiceProvider::class, // [tl!--] Illuminate\View\ViewServiceProvider::class, // [tl!++] ] ] ``` ================================================ FILE: content/collections/pages/3-3-to-3-4.md ================================================ --- id: a1d1a50e-fb42-4a9e-b03f-59dc47f21dc2 blueprint: page title: 'Upgrade from 3.3 to 3.4' intro: 'A guide for upgrading from 3.3 to 3.4. For most sites, the process will take less than 5 minutes.' template: page --- ## Overview First read through this guide to see if there's anything that you might need to adjust. When upgrading, Statamic may automate some things for you. They'll be noted below. In your `composer.json`, change the `statamic/cms` requirement: ```json "statamic/cms": "3.4.*" ``` Then run: ``` shell composer update statamic/cms --with-dependencies ``` ## High impact changes ### The runtime Antlers parser is the default In 3.3 we introduced a new Antlers parser but you had to opt into it. In 3.4 it becomes the default. You may continue to use the old "regex" parser, but you'll need to make sure to specify that in `config/statamic/antlers.php`. ```php 'version' => 'regex', // or "runtime" for the new parser ``` ### Bard addons / extensions will no longer work Bard's underlying editor, Tiptap, has been updated from v1 to v2 which required significant changes to Bard. This means that Bard addons will no longer work until they've been updated to support Statamic 3.4. You should check if the Bard addons you have installed have been updated for 3.4, or if they are even necessary anymore since the native Bard editor now has more features. [Read how to upgrade your addons for Bard 2](/upgrade-guide/bard-v1-to-v2) ### Draft entries no longer get added to search indexes In 3.3 drafts would be indexed, in 3.4 they are not. If you're linking a search index to a collection, when you use the control panel since drafts are no longer indexed they will not show up in results. You can cancel out that behavior by using a filter that allows everything: ```yaml # content/collections/articles.yaml search_index: articles ``` ```php // config/statamic/search.php 'articles' => [ 'driver' => 'local', 'searchables' => ['collection:articles'], 'filter' => fn () => true, // [tl! ++] ] ``` ## Medium impact changes ### Term queries return all localizations **This will not affect any native tags, like `taxonomy`.** It will only affect you if you are performing term queries in PHP, such as: ```php Term::all(); Term::query()->get(); ``` In 3.3, these would deduplicate the terms and only give you a single localization. i.e. If you had 2 sites configured, and 3 terms, you'd end up with 3 results. In 3.4, you will get all the localizations. i.e. You would get 6 results. To get the previous behavior (even though it was buggy, and the reason for the change), you can query for a specific site: ```php $query = Term::query(); $query->where('site', 'english'); // [tl! ++] $terms = $query->get(); ``` ### Search queries return Result classes **This will not affect the `search:results` tag.** This would only affect you if you were performing searches manually in PHP. Depending on what you were doing with the results, it's possible no changes would be necessary anyway. Search queries now return `Result` instances. Previously they returned instances of the actual searchable items. (e.g. an Entry) You can continue to get the underlying classes by mapping using the `getSearchable()` method. ```php $results = Search::index('default')->search('foo')->get(); $results = $results->map->getSearchable(); // [tl!++] ``` ## Low impact changes ### Slugs no longer include extra characters In some forms, the slug will get automatically generated. e.g. When you type into the `title` field on a publish form. In 3.3, some special characters would get converted. e.g. The `&` would become `and`. In 3.4, **these characters will not get converted**. To enable these character conversions, you can enable a new setting in `config/statamic/system.php`. ```php 'ascii_replace_extra_symbols' => true, ``` ### Replicator, Bard, and Grid IDs All three fieldtypes will now supply their `id` to templates. Previously, the `id` would have been the entry's ID. If you still need to access the entry's ID within the field tag pair, you may use the `page` variable: ``` {{ grid }} {{ id }} the entry id {{# [tl! --] #}} {{ id }} the grid item's id {{# [tl! ++] #}} {{ page:id }} the entry id {{# [tl! ++] #}} {{ /grid }} ``` ### LocalizedTerm reference now includes site handle The `reference` method of the `LocalizedTerm` class now includes the site handle, even when only a single site is configured. ```yaml term::categories::hats # [tl! --] term::categories::hats::en # [tl! ++] ``` ### Entries etc must implement Searchable The `Entry`, `LocalizedTerm`, `Asset`, and `User` classes must implement the `Searchable` interface. If you've customized any of these classes, you'll need to make sure to implement the appropriate methods. If you are extending from the native classes, then you probably don't need to make any changes. ### Custom search index drivers accept locale This only affects users that have created custom search index drivers. Now that search indexes can be localized, when they are constructed, a nullable `$locale` string will be passed to it. You should make sure that the name includes it. ```php Search::extend('custom', function ($app, $config, $name, $locale) { return new CustomDriver( $config, $name, $locale // [tl!++] ); }); ``` If you are extending the native Statamic `Index` class, this will be done for you. ### Search default index When using the `Search` facade, certain methods would apply directly to the default index. Since 3.4 introduces localized indexes, if your default index is localized, these magic methods will target the current site. For example: ```php // If currently on the "english" site, Search::for('foo'); // applies to english index // If currently on the "french" site Search::for('foo'); // applies to french index ``` If needed, you can be explicit: ```php Search::index('default', 'french')->for('foo'); ``` ### Search methods removed The `clearIndex` and `indexExists` methods have been removed. ## Zero impact changes These are items that you can completely ignore. They are suggestions on how to improve your code using newly added features. ### Utility and Permission registration 3.4 fixes an issue where translations for utilities and permissions may not have been displayed in the right language when a user has a custom locale preference. The core utilities and preferences were fixed, however in order to fix it for addons, you will need to wrap your existing code in closures. ```php Permission::extend(function () { // [tl! ++] Permission::register('foo', '...'); }); // [tl! ++] Utility::extend(function () { // [tl! ++] Utility::make('foo')->register(); }); // [tl! ++] ``` ### Utility fluent methods The utility class was updated to be consistent with permissions syntax. You no longer need to chain `register` to the end. You can simply use `register` at the start instead of `make`. ```php Utility::make('foo')->title('Foo')->etc()->register(); // [tl! --] Utility::register('foo')->title('Foo')->etc(); // [tl! ++] ``` ================================================ FILE: content/collections/pages/3-4-to-4-0.md ================================================ --- id: e077f513-45c1-4eff-ba87-210340dd6f54 blueprint: page title: 'Upgrade from 3.4 to 4.0' intro: 'A guide for upgrading from 3.4 to 4.0. For most sites (those running Laravel > 8), the process will take less than 5 minutes.' template: page --- ## Overview First read through this guide to see if there's anything that you might need to adjust. While there are many items on this page, a majority of them only apply to addons or custom code. We've noted who each item would apply to so you can more easily scan through the changes. ### Upgrade using Composer In your `composer.json`, change the `statamic/cms` requirement: ```json "statamic/cms": "3.4.*" // [tl!--] "statamic/cms": "^4.0" // [tl!++] ``` Then run: ``` shell composer update statamic/cms --with-dependencies ``` ## High impact changes ### PHP and Laravel support **Affects apps using PHP < 8 or Laravel < 9.** - The minimum version of PHP is now 8.0. - The minimum version of Laravel is now 9. ### AMP has been removed **Affects apps using the AMP feature.** AMP is considered a dead project, and no longer provides SEO benefits, so the entire AMP feature has been removed. ### API filters are opt-in **Affects apps using the GraphQL or REST API features.** All filters are disabled by default now for increased security. You must opt into each one you want made available. ```php // config/statamic/api.php or config/statamic/graphql.php 'resources' => [ 'collections' => true, // [tl!--] 'collections' => [ // [tl! ++:start] 'blog' => [ 'allowed_filters' => ['title', 'slug'] ] ] // [tl! ++:end] ] ``` If you try to use a filter that has not been explicitly allowed, it will result in a validation error. ## Medium impact changes ### Route namespaces have been removed **Affects apps or addons using PHP-based routes.** When using the following various methods of adding custom routes, previously Statamic would assume a namespace. In 4.0, the namespace is removed. Standard Laravel routes that you've added to your app routes files are not affected. - `Statamic::pushCpRoutes()` - `Statamic::pushWebRoutes()` - `Statamic::pushActionRoutes()` - Addon route files - Addon service provider `$this->registerCpRoutes()` - Addon service provider `$this->registerWebRoutes()` - Addon service provider `$this->registerActionRoutes()` For example, in an addon's `cp.php` routes file: ```php Route::get('example', 'ExampleController@foo'); // v3 = Your\Addon\Http\Controllers\ExampleController@foo // v4 = ExampleController@foo ``` In an addon, if you _want_ a namespace, you can add one with a property: ```php protected $routeNamespace = 'Your\Addon\Http\Controllers'; ``` However, we recommend using the class reference syntax: ```php use Your\Addon\Http\Controllers\ExampleController; Route::get('example', [ExampleController::class, 'foo']); ``` ### Str::replace arguments changed **Affects apps or addons using `Statamic\Support\Str::replace()`.** The `Statamic\Support\Str::replace()` method changed the argument order. The `$subject` argument moved from first to last. ```php Str::replace($subject, $search, $replace); // [tl! --] Str::replace($search, $replace, $subject); // [tl! ++] ``` ### Tailwind 3 **Affects apps or addons with custom CP components.** The Control Panel has been upgraded from Tailwind 1 to 3. The sizing scale has been adjusted. Custom components may render unexpectedly and may need to have classes renamed. ### Entry date behavior **Affects apps or addons with custom code.** When using the `date` method on an entry, an exception will be thrown if the entry is not in a dated collection. Because of this, if you are creating an entry instance, you should set the collection _before_ the date. ```php Entry::make() ->date($date) // [tl! --] ->collection($collection) ->date($date) // [tl! ++] ->set('foo', 'bar'); ``` ## Low impact changes ### Control Panel Composer actions have been removed **Affects users who are used to updating Statamic and addons using the Control Panel.** The ability to update Statamic, as well as installing and updating addons through the Control Panel has been removed. You will now need to use Composer on the command line. The Control Panel sections remain, however the buttons will now give you the Composer commands rather than running them for you. ### jQuery removed **Affects apps or addons with custom CP components using jQuery.** jQuery and jQuery UI were seldom used, and have been removed to lower the CP's overhead. We suggest replacing with Vue or Alpine equivalents. ### vue-reactive-provide removed **Affects custom CP Vue components using the `reactiveProvide` property.** The `vue-reactive-provide` package was removed. We suggest using providing an observed object. ```js reactiveProvide: { // [tl! --:start] name: 'foo', include: ['alfa', 'bravo'] } // [tl! --:end] provide: { // [tl! ++:start] foo: this.foo }, data() { foo: this.makeProvidedFoo(); }, methods: { makeProvidedFoo() { const foo = {}; Object.defineProperties(grid, { alfa: { get: () => this.alfa }, bravo: { get: () => this.bravo }, }); return foo; } } // [tl! ++:end] ``` ### Flysystem v1 support dropped **Affects apps or addons explicitly using Flysystem v1 code.** Support for `league/flysystem` v1 has been removed. Only v3 is supported. ### CommonMark v1 support dropped **Affects apps or addons with custom CommonMark v1 extensions.** Support for `league/commonmark` v1 has been removed. Only v2 is supported. ### Typography CSS styling **Affects apps or addons with custom CP components using the `clean-content` CSS class.** The custom `.clean-content` css class in the Control Panel has been replaced by `.prose` from Tailwind's typography plugin. ### FontAwesome and Entypo fonts have been removed **Affects apps or addons with custom CP components using these fonts.** Both icon fonts have been removed, and replaced with Streamline SVG icons. ### SortableList delay has been reduced to zero **Affects apps or addons with custom CP components using the `SortableList` component.** The delay was reduced from 200 to 0. The prop still exists, so you can manually bring it back. ```html ``` However, we suggest using a distance over delay in most cases. ```html ``` ### Panes have been removed **Affects apps or addons with custom CP components using the `Pane` component.** The "pane" feature has been removed. You can replace it with a narrow stack. ```html Some content ``` ### Color fieldtype has been simplified **Affects apps using the `color` fieldtype in their blueprints.** The color fieldtype now only supports hex values with no alpha channel. ### PortalVue and vue-js-modal components have been renamed. **Affects apps or addons with custom CP components using the `` or `` components.** Statamic v4 introduces our own `` component, so to prevent conflicts, the underlying PortalVue package's component has been renamed to ``. ```html ... ``` For consistency, we renamed the underlying modal component from `` to ``. ```html ... ``` ================================================ FILE: content/collections/pages/4-to-5.md ================================================ --- id: 91e8f239-2f99-47bc-b4dd-3518cd3e36ae blueprint: page title: 'Upgrade from 4 to 5' intro: 'A guide for upgrading from 4 to 5. For most sites (those running Laravel > 9), the process will take less than 5 minutes.' template: page --- ## Overview First read through this guide to see if there's anything that you might need to adjust. While there are many items on this page, a majority of them only apply to addons or custom code. We've noted who each item would apply to so you can more easily scan through the changes. ### Upgrade using Composer In your `composer.json`, change the `statamic/cms` requirement: ```json "statamic/cms": "^4.0" // [tl!--] "statamic/cms": "^5.0" // [tl!++] ``` Then run: ``` shell composer update statamic/cms --with-dependencies ``` ## High impact changes ### PHP and Laravel support **Affects apps using PHP < 8.1 or Laravel < 10.** - The minimum version of PHP is now 8.1. - The minimum version of Laravel is now 10. We highly recommend upgrading all the way to Laravel 11 and PHP 8.3. :::tip If you want to (semi-)automate the Laravel upgrade process, we recommend using [Laravel Shift](https://laravelshift.com/discounts/statamic-1983) (use that link for a special 19.83% discount 🤘). ::: ### Site configuration changes **Affects everyone.** _Note that Statamic will attempt to migrate this for you automatically during the upgrade._ The site config has been moved from `config/statamic/sites.php` into `resources/sites.yaml`. This allows you to manage your sites from the Control Panel. ```php // config/statamic/sites.php return [ // [tl! --:start] 'sites' => [ 'default' => [ 'name' => 'First Site', 'url' => config('app.url'), 'locale' => 'en_US', ], 'two' => [ 'name' => 'Second Site', 'url' => config('app.url').'/fr/', 'locale' => 'fr_FR', ] ] ]; // [tl! --:end] ``` ```yaml # resources/sites.yaml default: # [tl! ++:start] name: First Site url: '{{ config:app:url }}' locale: en_US two: name: Second Site url: '{{ config:app:url }}/fr/' locale: fr_FR # [tl! ++:end] ``` _**Note:** Text direction is now also automatic, [based on each site's language](/multi-site#text-direction). You do not need to migrate `direction` values to your `resources/sites.yaml`._ There is now also a new `multisite` boolean in `config/statamic/system.php`. Previously, the multi-site feature would be considered "enabled" as soon as you configured a second site. Now there is an explicit option to enable it. This should be set to `true` if you previously had 2 or more sites configured. ```php // config/statamic/system.php 'multisite' => true, ``` ## Medium impact changes ### Blueprint default value usage **Affects apps that have `default` defined in their blueprints or fieldsets.** In v4, setting a `default` value for a field would only make it show up in the respective publish forms. In v5, these values will actually be used where appropriate, such as within front-end templates. ```yaml handle: myfield field: type: text default: my default value ``` ```yaml title: My Entry # the "myfield" is missing ``` ```antlers {{ if myfield }}yes{{ else }}no{{ /if }}: {{ myfield }} v4 outputs: "no: " {{# [tl! --] #}} v5 outputs: "yes: my default value" {{# [tl! ++] #}} ``` In most cases, this is what you would have expected to happen anyway. ### Site methods **Affects apps or addons using the `Site::hasMultiple()` method.** Continuing from the multi-site configuration changes above, there are now two separate methods for determining multi-site state. - The new `Site::multiEnabled()` method will return true if the `multisite` boolean in `system.php` is enabled. - The existing `Site::hasMultiple()` method will return `true` if at least two sites are configured. You should decide whether each existing usage of `Site::hasMultiple()` should imply that the feature is enabled entirely, or if you need to actually count the number of sites. ### Laravel Helpers package has been removed **Affects apps or addons relying on methods from that package in custom code.** The `laravel/helpers` package provided support for the older global-style string/array/misc functions like `array_get`, `str_contains`, `snake_case`, `ends_with`, etc. You will now need to either require this package yourself, or update to use the underlying methods. For example: ```php namespace App\Example; use Statamic\Support\Arr; // [tl! ++,**] use Statamic\Support\Str; // [tl! ++,**] class Example { function example() { array_get($arr, $key); // [tl! --,**] Arr::get($arr, $key); // [tl! ++,**] str_start($str, $prefix); // [tl! --,**] Str::start($str, $prefix); // [tl! ++,**] ends_with($str, $needle); // [tl! --,**] Str::endsWith($str, $needle); // [tl! ++,**] } } ``` We recommend making the code changes. However, if you just want to require the package: ```sh composer require laravel/helpers ``` ### Statamic will now use your app's default pagination view **Affects apps using the `auto_links` pagination variable in templates.** Previously, Statamic used the `pagination::default` view to rendering pagination links. In Statamic 5, it will use your app's default pagination view, typically `pagination::tailwind`. To avoid making code changes, you may wish to change the default view back to `pagination::default`: ```php // app/Providers/AppServiceProvider.php use Illuminate\Pagination\Paginator; // [tl! ++] public function boot(): void { Paginator::defaultView('pagination::default'); // [tl! ++] } ``` ### Updated `please` file for Laravel 11 **Affects apps upgrading to Laravel 11 and the new application skeleton (including all upgrades via Laravel Shift).** Previously, our `please` command line utility assumed an `app/Console/Kernel.php` file existed in your application. However, with the introduction of Laravel 11, this file is no longer included with the new application structure. When upgrading apps to Laravel 11 with the new application skeleton, you'll need to update the `please` file in your app's root directory: ```php #!/usr/bin/env php handleCommand(new ArgvInput); exit($status); ``` ## Low impact changes ### Regex Antlers Parser has been removed **Affects apps that are still using the `regex` parser.** The new Antlers parser was introduced in 3.3 and was made the default in 3.4. You would have been using the old Parser if either of these apply: - In `config/statamic/antlers.php`, the `version` was set to `regex`. - The `antlers.php` file doesn't exist at all. The newer parser should be backwards compatible but if you encounter any errors, you may need to check the [docs](/antlers). ### Form submission path config **Affects apps that have customized the `submissions` path in `config/statamic/forms.php`.** The place to customize the directory for form submissions has moved from `forms.php` into `stache.php`. ```php // config/statamic/forms.php 'submissions' => 'custom path', // [tl! --] // config/statamic/stache.php 'stores' => [ 'form-submissions' => [ // [tl! ++:start] 'directory' => 'custom path', ], // [tl! ++:end] ], ``` ### Validation rule changes **Affects apps using `unique_entry_value`, `unique_term_value`, or `unique_user_value` rules.** _Note that assuming you haven't done anything too unusual, the following changes may have been performed automatically by Statamic during the update process._ We have updated our custom validation rules to use the more modern Laravel syntax. This means dropping the string based aliases in favor of classes. ```yaml validate: - 'unique_entry_value:{collection},{id},{site}' #[tl!--] - 'new \Statamic\Rules\UniqueEntryValue({collection},{id},{site})' #[tl!++] - 'unique_term_value:{taxonomy},{id},{site}' #[tl!--] - 'new \Statamic\Rules\UniqueTermValue({taxonomy},{id},{site})' #[tl!++] - 'unique_user_value:{id}' #[tl!--] - 'new \Statamic\Rules\UniqueUserValue({id})' #[tl!++] ``` ### Antlers sanitization **Affects apps or addons using the `sanitize` modifier or the `Html::sanitize` method.** The `sanitize` modifier (and `Html::sanitize()` method) method has changed under the hood from using `htmlentities` to `htmlspecialchars`. This change allows for things like accent characters (ü, í, etc) to remain unescaped. This is likely what you want to happen anyway, but if you have a reason for them to be converted, you should use `entities` modifier or `Html::entities()` method respectively. ### Seed removed from `shuffle` modifier **Affects apps using the shuffle modifier with an argument.** In Laravel 11, the underlying randomization technique was made more secure and no longer supports seeds. If you need to support seeds, you will need to use a custom modifier. ``` {{ my_array | shuffle:123 }} {{# [tl! --] #}} {{ my_array | custom_shuffle_with_seed:123 }} {{# [tl! ++] #}} ``` The shuffle modifier without an argument will continue to work without any modification needed. ### The `modify_date` modifier is now immutable **Affects apps using the modify_date modifier.** In Statamic 4, the `modify_date` modifier would modify date variable which would then be reflected elsewhere in your template. In Statamic 5, this is fixed, but if you were relying on this incorrect behavior you will need to handle it. ```antlers {{ date }} // 1st of may {{ date | modify_date('+1 day') }} // 2nd of may {{ date }} // 2nd of may {{# [tl! --] #}} {{ date }} // 1st of may {{# [tl! ++] #}} ``` ### The `svg` tag sanitizes by default **Affects apps that use the `svg` tag.** The `{{ svg }}` will now sanitize the output by default. This meant things like JavaScript or other valid but potentially insecure elements will be filtered out. For most people this won't be a problem but if you rely on this advanced SVG features, you may want to disable it. ```antlers {{ svg src="foo.svg" sanitize="false" {{# [tl! ++] #}} }} ``` Alternatively, you can opt out of it completely in a service provider: ```php public function boot() { \Statamic\Tags\Svg::disableSanitization(); // [tl! ++] } ``` ### Bard JS value is now an object **Affects apps or addons that are manually targeting Bard's value in JS** Previously, to prevent issues with how Laravel would trim whitespace on submitted strings, Bard's value would be a JSON stringified version of an object. In Statamic 5, it will just be the object. ```js let bardValue = JSON.parse(getBardValue()); // [tl! --] let bardValue = getBardValue(); // [tl! ++] ``` ### User roles inherit from groups **Affects apps or addons using the `roles`/`hasRole` methods on the `User` class, or the `user:is`/`is` tags.** In v4, the `User` class's `roles` method would only return roles defined explicitly on the user. It would not return roles inherited through any assigned groups. This affected calling the `roles` method directly, the `hasRole` method, or the `user:is` and `is` tags. This was a common point of confusion. So in v5, including the inherited roles is the more "default" behavior. ```php // To get explicit roles... $user->roles(); // [tl! --] $user->explicitRoles(); // [tl! ++] // To check if a user has a role explicitly defined... $user->hasRole($role); // [tl! --] $user->explicitRoles()->has($role->handle()); // [tl! ++] // To set explicit roles... $user->roles($roles); // [tl! --] $user->explicitRoles($roles); // [tl! ++] // To get all roles, including ones through user groups... getAllRoles($user); // (It was complicated) [tl! --] $user->roles(); // [tl! ++] // To check if a user has a role, including ones through user groups... getAllRoles($user)->has($role->handle()); // (It was complicated) [tl! --] $user->hasRole($role); // [tl! ++] ``` ### Misc class method changes The following methods have been removed: - `Statamic\Entries\Collection::revisions()` removed. Use `revisionsEnabled()`. The following interfaces have added `findOrFail()` methods: - `Statamic\Contracts\Assets\AssetContainerRepository` - `Statamic\Contracts\Assets\AssetRepository` - `Statamic\Contracts\Auth\UserRepository` - `Statamic\Contracts\Entries\CollectionRepository` - `Statamic\Contracts\Entries\EntryRepository` - `Statamic\Contracts\Globals\GlobalVariablesRepository` - `Statamic\Contracts\Structures\NavigationRepository` - `Statamic\Contracts\Taxonomies\TermRepository` The following methods have changed: - `Statamic\StaticCaching\Cacher::getCachedPage()` now returns a `Statamic\StaticCaching\Page`. ### Glide filename parameter has been removed This was an undocumented relic of a feature that let you add a "fake" or "vanity" filename to the end of Glide URLs to be able to improve SEO. This now happening automatically based on the original filename, rendering this feature unnecessary (this feature was broken in certain situations and as mentioned before — undocumented). If you use this `{{ glide filename="" }}` parameter, you'll need to remove it, otherwise nothing will be output. If you are hot-linking to any images rendered with manually set filenames in this way, you'll also need to correct those links. ### Entries may now only be queried by a single status **Affects any apps or addons filtering entries by `status`.** Previously, when querying entries, you could use *any* condition to filter entries. However, in v5, as part of improvements to how statuses work behind the scenes, statuses can now only be filtered using the `is` / `equals` conditions, and the `whereStatus` query method. Some examples: Collection tag: ```antlers {{ collection:blog status:in="published" }} {{# [tl! --] #}} {{ collection:blog status:is="published" }} {{# [tl! ++] #}} ``` PHP: ```php Entry::query() ->where('collection', 'blog') ->where('status', 'published') // [tl! --] ->whereStatus('published') // [tl! ++] ->get(); ``` REST API: ```php /api/collections/blog/entries?filter[status:in]=published // [tl! --] /api/collections/blog/entries?filter[status]=published // [tl! ++] ``` GraphQL API: ```graphql { entries( collection: "blog" filter: { status: { in: "published" } #[tl! --] status: "published" #[tl! ++] } ) { data { title } } } ``` If you need to query entries regardless of status, you can pass `any`. ## Zero impact changes These are items that you can completely ignore. They are suggestions on how to improve your code using newly added features. ### JS Slug generation The `$slugify` JS API has been deprecated. This could be a good time to use the new slug helpers. If you are generating simple slug/handles, you can use the global helper: ```js let slug = this.$slugify('Foo Bar'); // foo-bar [tl! --] let slug = str_slug('Foo Bar'); // foo-bar [tl! ++] let handle = this.$slugify('Foo Bar', '_'); // foo_bar [tl! --] let handle = snake_case('Foo Bar'); // foo_bar [tl! ++] ``` If your slugs need to factor in language logic (like an entry's slug would), then you can use the new server side feature: ```js let slug = getSlug('Foo Bar'); // [tl! --:start] function getSlug(value) { return this.$slugify(value); } // [tl! --:end] let slug = await getSlug('Foo Bar'); // [tl! ++:start] async function getSlug(value) { return this.$slug.async().create('Foo Bar'); } // [tl! ++:end] ``` ### Addon test case If you have an addon, there's a good chance your `TestCase` is a bit complicated. You should be able to extend the new `AddonTestCase` and specify your service provider in favor of manually wiring up all the Testbench bits. ```php use Orchestra\Testbench\TestCase as OrchestraTestCase; // [tl! --] use Statamic\Testing\AddonTestCase; // [tl! ++] abstract class TestCase extends OrchestraTestCase // [tl! --] abstract class TestCase extends AddonTestCase // [tl! ++] { protected string $addonServiceProvider = YourServiceProvider::class; // [tl! ++] protected function getPackageProviders($app) // [tl! --:start] { return [ GraphQLServiceProvider::class, StatamicServiceProvider::class, YourServiceProvider::class, ]; } // etc... [tl! --:end] } ``` ================================================ FILE: content/collections/pages/5-to-6.md ================================================ --- id: 9a013ab0-bd21-42e1-84ea-fecd052466e9 blueprint: page title: 'Upgrade from 5 to 6' intro: 'A guide for upgrading from 5 to 6. For most sites (those running Laravel >= 12), the process will take less than 5 minutes.' template: page --- ## Overview First read through this guide to see if there's anything that you might need to adjust. While there are many items on this page, a majority of them only apply to addons or custom code. We've noted who each item would apply to so you can more easily scan through the changes. ### Upgrade using Composer In your `composer.json`, change the `statamic/cms` requirement: ```json "statamic/cms": "^5.0" // [tl!--] "statamic/cms": "^6.0" // [tl!++] ``` Then run: ``` shell composer update statamic/cms --with-dependencies ``` ## High impact changes ### PHP and Laravel support **Affects apps using PHP < 8.3 or Laravel < 12.** - The minimum version of PHP is now 8.3. - The minimum version of Laravel is now 12. We highly recommend upgrading all the way to Laravel 13 and PHP 8.5. :::tip If you want to (semi-)automate the Laravel upgrade process, we recommend using [Laravel Shift](https://laravelshift.com/discounts/statamic-1983) (use that link for a special 19.83% discount 🤘). ::: ### Vue 3 **Affects apps or addons that use Vue.** We have upgraded the Control Panel's version of Vue.js from 2 to 3. To keep this upgrade guide manageable, we have a [dedicated page for upgrading from Vue 2 to Vue 3](/upgrade-guide/vue-2-to-3). If you do not have any custom Vue components in your app, or in your own addons, you can skip this. ### Timezones **Affects apps using dated collections or date fields** **If your `timezone` setting in `config/app.php` is set to `UTC`, then nothing will change for you.** Dates remain stored in your application's timezone. But now Statamic will convert them to UTC at runtime, which makes it much easier for Statamic to localize them as needed. This applies to dated entries or date fields. For example, if you have your timezone set to New York (GMT-5:00) and you have a date at 10pm, when it gets converted to UTC it will be 5 hours ahead - in the next day! ```php // config/app.php 'timezone' => 'America/New_York', ``` ```yaml # an-entry.md my_date_field: '2025-03-06 22:00' ``` ```php $entry->my_date_field; // 5.x: Carbon { 2025-03-06 22:00 America/New_York } [tl! --] // 6.x: Carbon { 2025-03-07 03:00 UTC } [tl! ++] ``` ::tabs ::tab antlers ```antlers {{ my_date_field | iso_format('JJJJ') }} 5.x: Thursday, March 6, 2025 10:00 PM {{# [tl! --] #}} 6.x: Friday, March 7, 2025 3:00 AM {{# [tl! ++] #}} ``` ::tab blade ```blade {{ Statamic::modify($my_date_field)->iso_format('JJJJ') }} 5.x: Thursday, March 6, 2025 10:00 PM {{-- [tl! --] --}} 6.x: Friday, March 7, 2025 3:00 AM {{-- [tl! ++] --}} ``` :: It's best practice to keep dates as UTC until you're ready to display them, which means modifiers will deal with UTC versions. But, you can opt into automatic conversion to your display timezone by changing the following in `config/statamic/system.php`: ```php 'localize_dates_in_modifiers' => true, // [tl! ++] ``` This settings _should_ have been automatically set to `true` by Statamic during the upgrade, but you should confirm it. For more information on how Timezones work in Statamic 6, please see the [Timezones guide](/knowledge-base/tips/timezones). #### Control Panel Dates in the Control Panel are now localized to the user's operating system timezone, rather than the application timezone. For example, on Statamic 5, if you were in a different timezone to what your app was configured in, and you select a date from the date picker, that date would be treated as the date for the app's timezone. Not your timezone. This was a common cause of confusion, which was one of the main reasons for all these changes. Now in Statamic 6, the date you pick will be the date in **your** timezone. There is nothing for you to change except your expectations when working with dates, and instructing your clients about it. #### REST API & GraphQL Dates will now be returned by Statamic's REST API and GraphQL API in UTC, allowing you to localize them as needed on your frontend. #### Changing your app timezone It's best practice to set your app's timezone to UTC. However, changing the timezone in an existing project is a big undertaking and could mean lots of content and dates need to be updated. Statamic 6 **does not** require that you change your timezone to UTC. But if you *want* to, we have provided a way to automate it. [Read how to change your timezone to UTC](/tips/change-timezone-to-utc). ## Medium impact changes ### Carbon 3 Support for [Carbon 2.x](https://carbon.nesbot.com/docs/) has been removed. All Statamic 6 sites now require [Carbon 3.x](https://carbon.nesbot.com/docs/#api-carbon-3). If you're using any of Statamic's `months_ago`, `weeks_ago`, `days_ago`, `hours_ago`, `minutes_ago`, and `seconds_ago` modifiers, you will notice that they now return floats instead of integers. Comparing against past timestamps will also result in negative numbers. You _may_ need to updates your templates to account for these changes. ### Globals We have made various changes to how globals are stored and localized. If you use globals in your app, please read through these changes and take any necessary action. #### Single site installs: Variables are now stored separately from the global set config Global Variables are now stored separately from the global set's config, allowing config and content to be properly separated. Instead of living under a `data` key in the global set's YAML file, they now live in a separate YAML file in a directory named after your default site, usually called `default`. **Before:** ```yaml # content/globals/seo.yaml title: SEO data: meta_description: Synthwave nostalgia with soaring sax and heartfelt vibes. meta_image: the-midnight.jpg ``` **After:** ```yaml # content/globals/seo.yaml title: SEO ``` ```yaml # content/globals/default/seo.yaml meta_description: Synthwave nostalgia with soaring sax and heartfelt vibes. meta_image: the-midnight.jpg ``` _This change may have been performed automatically by Statamic during the upgrade process._ **Note:** This change _doesn't_ affect multi-sites or sites storing global variables in the database, since they're already stored separately. #### Multi-sites: Localized sites are now determined by the `sites` array Previously, when you configured the sites a global set was localized into, it created the global variable files for you, then used the existence of those files to determine which sites the global set was localized into. Now, Statamic will use the `sites` array in the global set's config file to determine which sites the global set is localized into, as well as mapping the origins for localizations. ```yaml # content/globals/seo.yaml title: SEO sites: en: null fr: en # Localized from en de: null # No origin ``` _This change may have been performed automatically by Statamic during the upgrade process._ **Note:** This change _doesn't_ affect single-site installs. #### Events Previously, when saving global variables in the Control Panel, the entire global set would have been saved, causing the `GlobalSetSaving`, `GlobalSetCreated` and `GlobalSetSaved` events to be dispatched. However, now, only the global variable _itself_ will be saved. This means that if you were listening to any of these events to pick up changes to global variables, you should instead listen for the [`GlobalVariablesSaving`](https://statamic.dev/extending/events#globalvariablessaving), [`GlobalVariablesCreated`](https://statamic.dev/extending/events#globalvariablescreated) and [`GlobalVariablesSaved`](https://statamic.dev.test/extending/events#globalvariablessaved) events. #### Removed methods on `GlobalSet` class The `addLocalization` and `removeLocalization` methods have been removed from the `GlobalSet` class. If you were calling these methods in your app, you should update your code to call `save` and `delete` on the `Variables` class instead. ```php $globalSet->addLocalization($globalSet->makeLocalization('en')->data(['foo' => 'bar'])); // [tl! remove] $globalSet->removeLocalization('en'); // [tl! remove] $globalSet->in('en')->data(['foo' => 'bar'])->save(); // [tl! add] $globalSet->in('en')->delete(); // [tl! add] ``` ### Search: `'searchables' => 'all'` **Affects apps using `'searchables' => 'all'` in their search config.** Previously, you could set `'searchables' => 'all'` on a search index to include entries, terms, assets, users and anything provided by [custom searchables](/frontend/search#custom-searchables). However, in v6, to split out search between the frontend and the Control Panel, support for `'searchable' => 'all'` has been removed. You can now either use `'searchables' => 'content'` - which includes entries, terms and assets (**not** users) - or explicitly list [the searchables](/frontend/search#searchables) you want: ```php // config/statamic/search.php 'indexes' => [ 'default' => [ 'driver' => 'local', 'searchables' => ['collection:blog', 'taxonomy:categories', 'assets:*'], 'fields' => ['title'], ], ], ``` We’ve avoided automating this migration so you can intentionally decide whether users should be included. ### Breadcrumbs **Affects apps or addons displaying breadcrumbs in the Control Panel.** Breadcrumbs are now generated from items in the Control Panel navigation, rather than needing to be passed into views manually. You should remove references to the `Breadcrumb` class in your code, as well as the `` Vue component. ``` php use Statamic\CP\Breadcrumbs; // [tl! remove:start] $crumbs = Breadcrumbs::make([ ['text' => 'First', 'url' => '/first'], ['text' => 'Second', 'url' => '/second'], ]); return view('myview', ['crumbs' => $crumbs]);// [tl! remove:end] return view('myview'); // [tl! add] ``` ``` blade {{-- [tl! remove] --}} ``` ``` vue ``` To learn more about customizing breadcrumbs, please refer to the [CP Navigation documentation](/extending/cp-navigation#breadcrumbs). ### Starter Kits: Removed `export_as` option **Affects starter kits using the `export_as` option.** The `export_as` option has been removed in favor of the new `package` [folder convention](/starter-kits/creating-a-starter-kit#the-starter-kit-package), which makes dealing with multiple README.md files easier, for example. ### Custom Icons If you are using any `icon` fieldtypes with the `directory` option, you will need to register your icon set and reference the set name instead. ```php // AppServiceProvider.php use Statamic\Facades\Icon; // [tl! ++] public function boot(): void { Icon::register('heroicons', base_path('resources/heroicons')); // [tl! ++] } ``` ```yaml - handle: favourite_icon field: type: icon directory: resources/heroicons # [tl! --] set: heroicons # [tl! ++] ``` If you are using custom icons for your Replicator or Bard sets, the method changed: ```php use Statamic\Fieldtypes\Sets; Sets::setIconsDirectory(directory: 'path/to/heroicons'); // [tl! --] Sets::useIcons('heroicons', 'path/to/heroicons'); // [tl! ++] ``` Or if you need to do both, you can reference the set name: ```php Icon::register('heroicons', base_path('resources/heroicons')); Sets::useIcons('heroicons'); ``` ### Bard: `inline: break` **Affects apps using `inline: break` on Bard fields.** We've simplified the `inline` config option on Bard fields. It is now a toggle, as opposed to a select dropdown with various modes. If you were using the `inline: break` option, you should use the new `inline_hard_breaks` option instead: ```yaml inline: inline # [tl! --] inline: true # [tl! ++] inline_hard_breaks: true # [tl! ++] ``` ### Custom Control Panel Pages **Affects apps or addons with custom Control Panel pages.** If your app or addon includes custom Control Panel pages, we recommend migrating them to Vue with [Inertia.js](https://inertiajs.com/) for the best experience. This provides SPA-style page transitions and a more consistent experience alongside the rest of the Control Panel. See the [CSS & JavaScript](/control-panel/css-javascript#inertia) page for more details. For simpler addons, or if you prefer not to use Vue, you may continue to build Control Panel pages using Blade, but there are a few limitations to be aware of: - Blade-rendered pages trigger a full page reload rather than the SPA-style transitions used elsewhere in the Control Panel. - Under the hood, Blade views are rendered inside a Vue component, which means ` ``` ### Partials Statamic's [`{{ partial }}`](/tags/partial) tag allows you to include a view from within another view. All variables that are available to the parent view will be made available to the included partial view. ``` {{ partial:footer }} ``` Even though the included view will inherit all data available in the parent view, you may also pass an array of additional data that will be made available to the included view: ``` {{ partial:blog/card mode="stacked" }} ``` If you attempt to use a `partial` that doesn't exist, Statamic will throw an error. If you would like to include a partial that may or may not exist (for example, using a variable in the partial name), you should use the [`{{ partial:if_exists }}` ](/tags/partial-if-exists) tag. ``` {{ partial:if_exists src="blog/card" }} ``` All views inside your `/resources/views/` directory can be used as a partial, including [Blade](/blade) views. #### Slots Sometimes you might need to pass a large chunk of content into a partial. Jamming a bunch of HTML through a parameter would be like trying to shove a pizza through a donut. Entertaining, but futile. Slots are the solution. By using the `partial` tag as a pair, everything inside will be passed into the partial, mapped to the {{ slot }} variable. Let's look at an example "modal" type of design component. ``` {{# /resources/views/partials/modal.antlers.html #}} ``` We can now pass whatever we want into the slot by injecting content into the partial: ``` {{ partial:modal }}

    50% off everything, today only!

    Man eating banana on sale. {{ /partial:modal }} ``` #### Named slots Sometimes you might want to render multiple different slots in different locations inside a partial. Let's modify our example to allow of the injection of a "title" slot: ``` {{# /resources/views/partials/modal.antlers.html #}} ``` Now you can define the context of the named slot using the `slot:name` tag format. Any content not within an explicit `slot:name` tag will be passed to the partial in the `slot` variable. ``` {{ partial:modal }} {{ slot:header }} {{ svg src="icons/flag" class="w-4 h-4 mr-2" }} {{ /slot:header }} Man eating banana on sale. {{ /partial:modal }} ``` ### Stacks Antlers allows you to push template code to a "stack" which can be rendered somewhere else in your layout (most commonly) or another view. This can be particularly useful for specifying any JavaScript libraries required by your child views: ``` {{ push:scripts }} {{ /push:scripts }} ``` You may push to a stack as many times as needed. To render the complete stack contents, pass the name of the stack to the `{{ stack }}` tag: ``` {{ stack:scripts }} ``` If you would like to prepend content onto the **beginning** of a stack, you should use the `{{ prepend }}` tag: ``` {{ push:scripts }} This will be second... {{ /push:scripts }} {{# Later... #}} {{ prepend:scripts }} This will be first... {{ prepend:scripts }} ``` ### Once The `{{ once }}` tag allows you to define a portion of the template that will only be evaluated once per rendering cycle. This may be useful for pushing a given piece of JavaScript into the page's header using [stacks](#stacks). For example, if you are looping through entries and rendering them with a partial, you may wish to only push the JavaScript to the header once, not every single time. ``` {{ collection:blog }} {{ once }} {{ push:scripts }} {{ /push:scripts }} {{ /once }} {{ partial:blog/card }} {{ /collection:blog }} ``` ### Section & Yield You may find that you wish to define areas of a layout that may need to change depending on which template is being rendered. Let's peek at this basic layout as an example: ``` {{ title }} / {{ site:name }}
    {{ template_content }}
    {{ yield:footer }}
    This is the main footer
    {{ /yield:footer }} ``` Notice the [`yield`](/tags/yield) tag. The contents of that tag will be rendered unless another template injects content into it using the [`section`](/tags/section) tag. ``` {{# /resources/views/landing/special.antlers.html #}} {{ section:footer }}

    Hi, I am a special footer! 👋

    {{ /section:footer }} ``` ## Prevent parsing You may find you need to prevent Antlers statements from being parsed. This is common when working with a JavaScript library like [Vue.js](https://vuejs.org), writing code examples, like we do in these docs. In either case, you have a few options. ### The `@` ignore symbol First, you may use an `@` symbol on the outside of your curly braces to tell Antlers to leave it alone like a jellyfish on the beach. The `@` symbol will be stripped out automatically leaving nothing but your full expression behind. ``` Hey, look at that @{{ noun }}! ``` ``` html Hey, look at that {{ noun }}! ``` The `@` can also be used to escape individual braces within tag parameters or strings. ``` {{ partial:example attributes="class='@{font-bold: isImportant@}'" }} // attributes="class='{font-bold: isImportant}'" ``` ``` {{ "string @{foo@} bar" }} // "string {foo} bar" ``` ### Ignoring Tag parameters You may ignore the contents of tag parameters by prefixing the parameter with a backslash. This could be useful allow you to avoid having to escape each curly brace like the example above if you are providing some JS/JSON in a parameter: ``` {{ form:create \x-data="{ submittable: false }" }} ``` ### The `noparse` Tag Use this method if you need to prevent entire code blocks from being parsed. ``` {{ noparse }} Welcome to {{ fast_food_chain }}, home of the {{ fast_food_chain_specialty_item }}, can I take your order? {{ /noparse }} ``` ## Using Antlers in content Antlers template code inside your content **is not** parsed automatically for security and performance reasons. You may **enable** Antlers parsing on a per-field basis by setting `antlers: true` in a given field's blueprint config. ### Opting into tags and modifiers {#allowing-tags-and-modifiers-in-content} When Antlers parses content (fields with `antlers: true`, or anything run through `Antlers::parse()`), it runs in a hardened mode that disables PHP syntax and restricts which tags and modifiers are available. This is **not** the same as a regular `.antlers.html` view — views still get the full, unrestricted Antlers experience. #### What's allowed by default Out of the box — with the `allowedContentTags` and `allowedContentModifiers` keys unset (the default shipped state) — you get: **Default tags:** - `link:*` - `obfuscate:*` - `trans:*` - `trans_choice:*` - `widont:*` - Any tags you've created in your own `App\Tags\` namespace (auto-allowed) **Default modifiers:** The broad set of safe built-in modifiers, including: - `markdown` - `sanitize` - `upper` - `lower` - `format` - `where` - `excerpt` - `nl2br` - `slugify` - ~150 others - Custom modifiers in your `App\Modifiers\` namespace (auto-allowed) The full list lives in `Statamic\Providers\ViewServiceProvider::defaultAllowedContentModifiers()`. The defaults are deliberately narrow. Any tag that can **fetch or expose site data** is excluded, because a content author typing `{{ collection from="private_drafts" }}` or `{{ users }}` into an `antlers: true` field could otherwise "leak" data they they may or may not be entitled to. If you want those tags in content, opt in explicitly. #### Common tags you may want to opt into These aren't on by default — add them to `allowedContentTags` if your editors need them: | Tag | Why it's not default | | --- | --- | | `collection:*` | Fetches entries from any collection | | `taxonomy:*` | Fetches terms from any taxonomy | | `nav`, `structure:*` | Fetches navigation trees | | `users:*` | Fetches user data | | `form:*` | Fetches form fields and submissions | | `assets:*`, `asset:*` | Fetches assets from any container | | `glide:*` | Image manipulation (generally safe, just not in the default set) | | `search:*` | Runs search queries | | `get_content`, `get_files` | Arbitrary data fetching | | `relate:*` | Follows relationships | #### Extending or replacing the defaults If you need to allow additional tags or modifiers (like `{{ glide }}` or `{{ nav }}`), opt into them via `config/statamic/antlers.php`. Use the `@default` token to **keep the defaults and add to them** — without it, your array **replaces** the defaults entirely: ```php // config/statamic/antlers.php return [ 'allowedContentTags' => [ '@default', // keep the built-in defaults 'glide:*', // allow {{ glide }}, {{ glide:src }}, etc. 'nav', // allow just {{ nav }} ], 'allowedContentModifiers' => [ '@default', // keep the built-in defaults 'my_custom', ], ]; ``` Tag entries are **patterns** — append `:*` to allow the tag and any of its parameters/sub-tags (`glide`, `glide:src`, `glide:generate`, etc). Modifier entries are exact handle matches. ### Allowing config values Referencing config in content fields (e.g. `{{ config:app:url }}`) goes through a separate allowlist. Statamic's `@default` list covers the common safe keys. If you're pulling a custom key, or chaining modifiers onto a config value, add it to `view_config_allowlist` in `config/statamic/system.php`: ```php // config/statamic/system.php 'view_config_allowlist' => [ '@default', 'app.url2', 'services.stripe.key', ], ``` ## Code comments {#comments} Antlers code comments are not rendered in HTML (unlike HTML comments), which allows you to use them to "turn off" chunks of code, document your work, or leave notes and inside jokes for yourself and other developers. ``` {{# Remember to replace the lorem ipsum this time, Karen! #}} {{#

    {{ title }}

    {{ date }}
    {{ content }}
    #}} ``` ## Using PHP in Antlers You can write PHP inside special delimiters. You may use `{{?...?}}` to write raw PHP and manipulate the current context (variables that exist in a given request), and `{{$...$}}` to `echo` the result of a PHP expression and render HTML. ### Syntax The following two syntax examples are functionally equivalent, but each uses a different approach based on the delimiter. ```antlers {{? $register = route('account.register'); ?}} Register for a new account ``` ``` antlers Register for a new account ``` ### Accessing Data in PHP Data in the [Cascade](/data-inheritance) can be accessed in much of the same way it is inside of a regular Antlers expression. ```antlers

    {{? $page->title ?}}

    {{? $globals->get('company_address') ?}}

    ``` ### PHP File Extension You can also change your view's file extension from `.antlers.html` to `.antlers.php` and you can write all the raw PHP you want using native PHP tags. ```php ``` ## Debugbar profiler 🎊 {#debugbar-profiler} Antlers has an experimental new Profiler tab in the [Debugbar](/debugging#debug-bar) that helps you see the performance impact of all of your template code.
    Antlers Profiler
    Profiling some Antlers code.
    Inside this Profiler there are 3 separate views that give you different glimpses into your site. ### View graph This view groups your Antlers expressions by the view files (templates, layouts, and partials) they exist in, allowing you to more easily tease out the location of any potential slowdowns or redundant calls. Each parsed expression in this view gets its own row in the table that shows various metrics and details that may prove to be useful. ### Expression graph This view shows all parsed expressions in a given request, listing them in **execution order**. Each parsed expression in this view gets its own row in the table that shows various metrics and details that may prove to be useful. ### Source view The Source View shows the final rendered template and highlights any content rendered by Antlers with a color corresponding to how fast it was executed. Green is fast, Yellow is a little slow, and Red is very slow. ### Profiler columns | Column | Explanation | |--|--| | Time | Starting from 00:00, exactly when on the timer this expression was run. This helps to see the order your code is executed in. | | Type | Shows whether the expression is an imported view, a variable, or a [Tag](/tags). | | View Path | When using the **Expression Graph**, shows the path to the view file the expression exists in. | | Line | Shows what line the expression is in. If you have configured the debugbar by publishing its config and have specified which code editor you use, you can click the line and open your editor straight to this bit of template code. | | Memory Usage | How much memory was used to execute this expression. | | Execution | The number of separate times the expression was executed. | | Tag Time | The amount of time it took to run the expression | | Total Time | The amount of time it took to run the expression along with any child expressions (e.g. a tag pair) | | % | The percentage of total load time dedicated to run this expression. | ### How to use the profiler If you have some pages in your site that are running slow, the Profiler can help you narrow down and find bottlenecks in your template code. Look for anything colored — yellow, orange, and red all _may_ point to some logic that is performing extra slow. Slow expressions don't necessarily mean that _Antlers_ is slow at parsing them, but rather Statamic is doing a lot of work in order to fetch, filter, and/or manipulate your data to render your view. With this information you can look for opportunities to cache bits of your template, open support requests and get clarification, ask questions in Discord, or pop the hood on your custom code to see what the hold up is. :::tip This feature is new and experimental! It's recommendations and "slow code" thresholds may need to be updated after getting more real world data. ::: ## Even more advanced stuff [John Koster's blog](https://stillat.com/blog) is full of really useful tips and tricks covering some really advanced features not documented here. Be sure to check it out! ## Thank you to John Koster 👏 This Antlers parser was a huge rewrite by the incomparable [John Koster](https://github.com/JohnathonKoster), who apparently found it a relaxing break from his day job. You can see the effort involved in this [massive PR](https://github.com/statamic/cms/pull/4257). We owe him a debt of gratitude for this amazing gift. ================================================ FILE: content/collections/pages/assets.md ================================================ --- id: 7277432d-bb25-458a-a3a2-a72976b44ad5 blueprint: page title: Assets intro: 'Assets are files managed by Statamic and made available to your writers and developers with tags and fieldtypes. They can be images, videos, PDFs, or any other type of file. Assets can have fields and content attached to them, just like entries, making them very powerful.' template: page related_entries: - 5b748a3f-be0e-41c1-8877-73f6b7ee1d0a - b70a3d9a-6605-446e-b278-de99ba561fe0 - 7277432d-bb25-458a-a3a2-a72976b44ad5 - 0c30a664-9bc3-4c5e-ad8c-66452b049748 - b50310b0-64ae-4ae4-b219-a637ed89e4d7 - 458b8203-e330-4d78-9bf5-82aaec8d458b - d0c65546-74f1-4a15-89d5-1562a95ee2c6 - 420f083d-99be-4d54-9f81-3c09cb1f97b7 updated_by: 3a60f79d-8381-4def-a970-5df62f0f5d56 updated_at: 1633025886 --- ## Overview Assets live in directories on your local server, in an [Amazon S3 bucket](https://aws.amazon.com/s3), or other cloud storage services. Each defined location is called a **container**. Statamic scans the files in each container and caches [meta information](#metadata) (like `width` and `height` for images) on them. This cache is used to speed up interactions and response times when working with them on the [frontend](/frontend) of your site. ## Asset browser You can explore these files in the Control Panel's asset browser. You can edit, sort, search, move, rename, replace, reupload, preview, and — if working with images — even set focal crop points to make dynamically resized images look their best.
    Assets browser Assets browser
    Browsing some assets.
    ## Asset actions There are a number of actions that can be taken on assets while in the asset browser. Some can be run in bulk (on multiple assets at once), while others are only available on individual assets. Single asset actions are available by clicking the options menu (three-dot icon) associated with the asset, and picking the desired action from the dropdown list. Bulk asset actions are available in a floating toolbar at the bottom of the asset browser whenever you have one or more assets selected.
    Assets actions Assets actions
    Check out those sweet actions.
    ### Edit Editing an asset opens a new modal window with a number of additional options, as well as any blueprint fields, like title, alt text, description, or other meta data defined on your asset container. Most of the asset actions are also available inside the editor, along with the ability to set a Focal Point for images.
    The Statamic Asset Editor The Statamic Asset Editor
    The asset editor is pretty slick, if we say so ourselves.
    ### Crop The crop action lets you visually crop an image directly in the Control Panel. It's available from the toolbar inside the [Asset Editor](#edit) for any image asset (except GIFs) when the current user has permission to upload to the container. You can drag to define a custom crop area, or pick one of the [aspect ratio presets](#crop-aspect-ratios). A flip button rotates the ratio between landscape and portrait orientation. Hold the Option / Alt key while resizing to resize from the center, and press Enter to apply the crop. After cropping, you'll be asked whether you want to save the crop as a **new copy** (uploaded to the same folder with a timestamped filename) or **replace the original** image. Replacing requires the user to also have the `reupload` permission on the asset. :::tip Cropping external images (for example, from an S3 container on a different domain) requires that the source be served with proper CORS headers. If the image can't be loaded cross-origin, the crop editor will warn you and close. ::: Bulk : No #### Crop aspect ratios Statamic ships with five aspect ratio presets (`16:9`, `4:3`, `3:2`, `2:1`, and `1:1`) available in the crop editor. You can customize them — or remove the dropdown entirely — via the `crop_aspect_ratios` array in `config/statamic/assets.php`. Each entry can be a `W:H` string, or an array with a `label` and a `ratio`. Labels are passed through Laravel's translator, so you can use translation keys to localize them. ```php // config/statamic/assets.php 'crop_aspect_ratios' => [ '16:9', '4:3', ['label' => 'Wide', 'ratio' => '16:9'], ['label' => 'US Letter', 'ratio' => '8.5:11'], ['label' => 'Golden', 'ratio' => 1.618], ], ``` Set `crop_aspect_ratios` to an empty array to hide the preset dropdown entirely and force users to drag custom selections. ```php 'crop_aspect_ratios' => [], ``` ### Copy URL Running this action allows you to copy the URL of an asset. You can use the copied URL to share or reference the asset in other places, such as in emails, documents, or on other websites. Bulk : No ### Download With this action, you can download an asset to your local device. It allows you to save a copy of the asset on your computer, making it accessible even when you're offline or outside Statamic. Bulk : Yes ### Duplicate The duplicate action creates a copy of an asset. It's useful when you want to have multiple copies of the same asset, either for organizational purposes or to make variations or modifications to the duplicated version without affecting the original asset. When duplicated, the new filename will be appended with `-{numberOfDuplicates}`. If you duplicate a file 3 times, you will have new copies named `yourFile-1.ext`, `yourFile-2.ext`, `yourFile-3.ext`. Feel free to rename these. In fact, we encourage it. Bulk : Yes ### Move Moving an asset involves changing its location within the folder structure of your Statamic assets. This action is handy when you want to reorganize your assets or place them in a different folder for better categorization and management. Assets moved with the move action will update any references to it throughout your content wherever the [Assets field](/fieldtypes/assets) is used. Bulk : Yes ### Rename As the name suggests, the rename action allows you to change the name of an asset. It's useful when you want to give a more descriptive or meaningful name to an asset or when you need to update the name to match changes in its content. Assets renamed with the rename action will update any references to it throughout your content wherever the [Assets field](/fieldtypes/assets) is used. Bulk : Yes* _*Each rename action only accepts one new filename, so this is only useful in bulk for renaming files of different extensions._ ### Replace The replace action lets you replace an existing asset with a new version with a new filename. This helps to ensure that your visitors don't run into browser-cached, old versions of your assets. Replaced assets with the replace action will update any references to it throughout your content wherever the [Assets field](/fieldtypes/assets) is used. Bulk : No ### Reupload Reuploading an asset involves uploading a new version of an existing asset, effectively replacing the previous version with the **same exact filename**. Keep in mind that by not changing the filename, your visitors may encounter browser-cached, old versions of the asset. Bulk : No ### Delete The delete action removes an asset from your site and server, permanently. Exercise caution when using this action, as deleted assets cannot always be easily restored. Bulk : Yes ## Asset fields Asset fields are configured like a [blueprint](/blueprints) and attached to the [container](#containers). Whenever you edit an asset in the Control Panel, you'll see the fields from the configured blueprint. This data is stored in the asset's [meta data](#metadata) file.
    The asset editor editing an image The asset editor editing an image
    Editing an image with the asset editor.
    ## Metadata Asset metadata is stored in YAML files inside a hidden `.meta` subdirectory inside each container. For example, `images/tree.jpg` gets an `images/.meta/tree.jpg.yaml` cache file. These files contain cached data, including but not limited to: image dimensions, file size, last modification dates, and so on. These cache files can also contain user created data. The fields are defined by the asset container's blueprint. Typically these are alt text, focal points, descriptions, and so on, but they could be anything you want at all. ``` yaml size: 9151 last_modified: 1558533973 width: 216 height: 104 data: alt: 'A tree with a tire swing' focus: 54-54-1 ``` :::tip You should consider version controlling these files if you plan to set data like alt tags and focal points. Make sure your efforts are preserved. ::: ### Cleaning orphaned metadata When asset files are deleted outside of Statamic (e.g., directly via the filesystem or an S3 console), their metadata `.yaml` files can be left behind. Run the `assets:meta-clean` command to find and remove these orphaned metadata files, along with any now-empty `.meta` directories. ``` shell php please assets:meta-clean ``` Pass a container handle to scope the cleanup to a single container, or use `--dry-run` to preview what would be deleted without making any changes. ``` shell php please assets:meta-clean images --dry-run ``` ## Containers Each container has its own settings, configurable permissions, and [blueprint](#blueprints). One container might be a local filesystem with upload, download, rename, and move permissions enabled, and another could be a read-only remote S3 bucket or stock image service. Containers can be created through the Control Panel and are defined as YAML files located in `content/assets`. Each container's filename becomes its `handle`. ``` yaml # content/assets/assets.yaml title: 'Assets' disk: 'assets' ``` Each container implements a "disk", also known as a [Laravel Filesystem](https://laravel.com/docs/filesystem). This native Laravel feature groups a [driver](#drivers), URL, location, and [visibility](#container-visibility) together. Statamic includes a local disk on fresh installs. You can modify or delete it, but many sites can simply use it as is. ``` php 'disks' => [ 'assets' => [ 'driver' => 'local', 'root' => public_path('assets'), 'url' => '/assets', 'visibility' => 'public', // (more info about visibility below) ], ] ``` Filesystems are defined in `config/filesystems.php`. They can point to the local filesystem, S3, or any [Flysystem adapter](https://flysystem.thephpleague.com/v2/docs/). ### Private containers Sometimes it’s handy to store assets that shouldn’t be publicly visible through a direct URL or browser. :::tip If your asset container's disk does not have a `url` property, Statamic will not output URLs. ::: Private containers should be located above webroot. If you leave the disk within the webroot, the files will still be accessible directly outside of Statamic if you know the file path. ``` files theme:serendipity-light / app/ content/ config/ public/ not-in-here/ # [tl! ~~] index.php put-it-out-here/ # [tl! ~~] resources/ vendor/ ``` Make sure to also set the [visibility](#container-visibility) to `private`. ### Container visibility Your filesystem's disk can have a `visibility`, which is an abstraction of file permissions. You can set it to `public` or `private`, which essentially controls whether they're accessible or not. Be sure to set `'visibility' => 'public'` if you want to be able to see, interact with, and manipulate files in your container. :::tip If you're using a service based driver like Amazon S3, and you want the files to be accessible by URL, make sure you set the [visibility](#container-visibility) to `public`. ::: ## Blueprints The default container [Blueprint](/blueprints) contains a single "alt text" field — just useful and simple enough to get you started. You can customize the fields on the blueprint by visiting the container in the Control Panel and choosing "Edit Blueprint" in the options dropdown. If you want to edit the blueprint file directly, you can do so in `resources/blueprints/assets/{handle}.yaml`. ## Ordering ### Default sort order in listings You can choose which field and direction to sort the list of assets in the Control Panel by setting the `sort_by` and `sort_dir` variables in your container.yaml. By default the file name will be used. ## Drivers Statamic uses Flysystem and includes the core `local` driver. S3, SFTP, and other drivers can be [installed with composer](https://laravel.com/docs/filesystem#driver-prerequisites). Flysystem is not limited to these three, however. There are adapters for many other storage systems. You can [create a custom driver](https://laravel.com/docs/filesystem#custom-filesystems) if you want to use one of these additional adapters in your Laravel application. ## Frontend templating {#templating} There are two main methods for working with Asset data on the frontend. The Assets Fieldtype, and the Assets Tag. ### Assets fieldtype The [Assets Fieldtype](/fieldtypes/assets) can be used in your content Blueprints to attach assets to your entries, taxonomy terms, globals, or user accounts. It can be used to create image galleries, video players, zip downloads, or anything else you can think of. All of the data stored on your Assets will be available on the frontend without having to create any kind of duplication. #### Example If you had a `slideshow` field with a whole bunch of images selected, you can render them by looping through them. ::tabs ::tab antlers ```antlers
    {{ slideshow }} {{ alt }} {{ /slideshow }}
    ``` ::tab blade ```blade
    @foreach($slideshow as $image) {{ $image->alt }} @endforeach
    ``` :: Learn more about the [Assets Fieldtype](/fieldtypes/assets). ### Assets tag If you ever find yourself needing to loop over all of the assets in a container (or folder inside a container) instead of selecting them manually with the Assets Fieldtype, this is the way. #### Example ::tabs ::tab antlers ```antlers {{ assets container="photoshoots" limit="10" sort="rating" }} {{ alt }} {{ /assets }} ``` ::tab blade ```blade {{ $alt }} ``` :: Learn more about the [Assets Tag](/tags/assets) and what you can do with it. ### Manipulating images Statamic uses the [Glide library](https://glide.thephpleague.com/) to dynamically resize, crop, and manipulate images. It's really easy to use and has [its own tag](/tags/glide). ::tabs ::tab antlers ```antlers {{ glide:image width="120" height="500" filter="sepia" }} ``` ::tab blade ```blade {{-- Using Statamic Tags --}} {{-- Using Fluent Tags --}} {{ Statamic::tag('glide') ->src($img) ->width(120) ->height(500) ->filter('sepia') ->fetch() }} ``` :: ## Search indexes You can configure search indexes for your collections to improve the efficiency and relevancy of your users searches. Learn [how to connect indexes](search#connecting-indexes). ## Allowed file extensions For security reasons, Statamic restricts the file extensions that can be uploaded via the Control Panel and the Assets field on [Forms](/forms). Common extensions like `.jpg`, `.csv` and `.txt` are permitted by default. To upload additional file extensions, specify them in `config/statamic/assets.php`: ```php // config/statamic/assets.php 'additional_uploadable_extensions' => [ 'gpx', 'vcf', // ... ], ``` ## Upload validation Each [container](#containers) can define [Laravel validation rules](https://laravel.com/docs/validation#available-validation-rules) that are applied to every file uploaded to it — through the Control Panel asset browser, the [Assets fieldtype](/fieldtypes/assets), or [Forms](/forms). You can configure rules in the Control Panel by editing the container and filling out the **Validation Rules** field, or by editing the container's YAML file directly: ``` yaml # content/assets/images.yaml title: Images disk: assets validate: - 'mimes:jpg,jpeg,png,webp' - 'max:2048' - 'dimensions:min_width=600,min_height=600' ``` Rules are merged with Statamic's built-in `file` and [allowed extension](#allowed-file-extensions) checks, so you only need to specify the additional constraints you care about. Failing uploads return a `422` response and surface the first validation message in the uploader UI. ## Filename character replacements When files are uploaded, Statamic sanitizes the filename by replacing a handful of characters (spaces, `#`, `:`, `/`, `\`, `?`, `<`, `>`, `"`, `|`, `*`, `%`, `'`, and double dashes) with a single dash to keep filenames URL-safe. If you need to replace additional characters — for example, commas and parentheses that clients keep sneaking their ways into filenames — you can add them to `config/statamic/assets.php`. These are **merged** with the native replacements and cannot override them. ```php // config/statamic/assets.php 'additional_filename_replacements' => [ ',' => '', '(' => '', ')' => '', ], ``` With the config above, `My Photo, (v2).jpg` would be saved as `my-photo-v2.jpg`. ## SVG sanitization For security reasons, Statamic automatically sanitizes uploaded SVG files. However, if you **trust your users** and need to upload SVG files without them being sanitization, you may disable it: ```php // config/statamic/assets.php 'svg_sanitization_on_upload' => false, ``` ## Video thumbnails Statamic can generate thumbnails for video assets so they display alongside images in the Control Panel's asset browser, instead of showing a generic file icon. ### Requirements Video thumbnail generation relies on [FFmpeg](https://ffmpeg.org/) being installed and available on your server. ``` shell # macOS (Homebrew) brew install ffmpeg # Ubuntu/Debian sudo apt install ffmpeg ``` If FFmpeg isn't found on the system `PATH`, you can point Statamic at the binary explicitly in `config/statamic/assets.php`: ```php // config/statamic/assets.php 'ffmpeg' => [ 'binary' => '/usr/local/bin/ffmpeg', 'cache_path' => storage_path('statamic/glide/ffmpeg'), ], ``` Generated thumbnails are cached to disk at `cache_path` so FFmpeg only runs once per video. ### Disabling Video thumbnail generation is enabled by default. To disable it, set `video_thumbnails` to `false` in `config/statamic/assets.php`: ```php //config/statamic/assets.php 'video_thumbnails' => false, ``` ## Custom cache stores Statamic leverages [Laravel's application cache](https://laravel.com/docs/cache) to cache asset metadata and folders. However, this means that whenever you run `php artisan cache:clear`, the cached asset information will be cleared. If you have a lot of assets and/or folders, you might want to specify a custom cache store so the cached assets are persisted when you clear your application cache. The cache store can be customized in `config/cache.php`. ```php // config/cache.php 'asset_meta' => [ 'driver' => 'file', 'path' => storage_path('statamic/asset-meta'), ], 'asset_container_contents' => [ 'driver' => 'file', 'path' => storage_path('statamic/asset-container-contents'), ], ``` To clear these caches, run `php please assets:clear-cache`. ## Performance If you're using [custom asset cache stores](#custom-cache-stores) and you're experiencing performance issues with Assets, like slow queries or a slow asset browser, it might be worth moving your assets to the database using the Eloquent Driver. It takes a different approach to caching asset metadata, which sometimes works better for sites with more assets. You can find out more about [moving assets to the database here](/tips/storing-content-in-a-database#moving-content-to-the-database). ================================================ FILE: content/collections/pages/augmentation.md ================================================ --- id: 9b2f6f55-5355-4334-b90d-d1236fb58887 blueprint: page title: Augmentation intro: 'Augmentation automatically transforms the rendered output of all Blueprint-defined variables based on their fieldtype.' --- ## Overview Augmentation is kind of like magic. ✨ For example, while using a [Markdown field](/fieldtypes/markdown), your content will **automatically be converted to HTML** without needing to use a [markdown modifier](/modifiers/markdown). Each [fieldtype](/fieldtypes) documents if and how augmentation affects output. :::hint Variables created "on the fly" with Front Matter won't be augmented. ::: ## Example Let's look at an example with and without augmentation using the following `content`: ``` md ## How to jump higher Bend your knees more and then spring upwards a _lot_ faster. ``` ### With augmentation If you're using a [Markdown field](/fieldtypes/markdown), the output will be as follows: ```html

    How to Jump Higher

    Bend your knees more and then spring upwards a lot faster.

    ``` ### Without augmentation While using a [Textarea field](/fieldtypes/textarea) — which _is not_ augmented — the output will be exactly the same as the input: ```text ## How to Jump Higher Bend your knees more and then spring upwards a _lot_ faster. ``` ## Digging deeper ### What is Augmentation? As explained above, augmentation lets Statamic convert one simple thing into a more complicated thing. If you're familiar with Laravel, you can think of it like a `toArray()` method converting something into a different representation of itself. However, Augmentation does this in a more on-demand way to prevent extra overhead where possible. There are two "augmentable" things: Field values, and objects. #### Field Values A field defined in a Blueprint will have a fieldtype associated with it. The raw value of the field can be converted by its fieldtype. This is packaged together inside a [Value](#value) object. For example, an [entries fieldtype](/fieldtypes/entries) will save an array of IDs. Simple strings. The fieldtype will know how to convert those IDs into Entry objects. #### Objects Classes like `Entry`, `Asset`, and `User` are [Augmentables](#augmentable), which means they're able to convert themselves to a bunch of usable values. Similar to the `toArray` method. For example, a Statamic Asset may have a path and some alt text. However it also has the ability to check the file size, dimensions, timestamp, and so on. ### Where do items get augmented? The two main areas where augmentation is leveraged is in Antlers templates, and the API. #### Antlers Templates Antlers templates handle Augmentable and Value classes. - When it encounters an [Augmentable](#augmentable) object, it will convert it to its [Augmented](#augmented) counterpart. (Which is filled with Value classes) - When it encounters a [Value](#value) object, it will know when to augment it. For example, when you visit an entry's URL, its values are provided to the template as Value objects. None of the values have been augmented yet. It will only happen if/when the variable is used in the template. #### REST API Similar to Antlers templates, when viewing an item via the API, you will see its augmented version. For example, when viewing an Asset, you will get all the extras like filesize, dimensions, etc. ``` json // /api/endpoint/asset/foo.jpg { "path": "foo.jpg", "alt": "a thing", "width": 600, "height": 400 } ``` If you use the `fields` filtering feature of the API, only the corresponding items will be fetched. Here, the width and height will never need to be evaluated at all. ``` json // /api/endpoint/asset/foo.jpg?fields=path,alt { "path": "foo.jpg", "alt": "a thing" } ``` ### Value As mentioned earlier, the `Statamic\Field\Value` class wraps up field's raw value and the fieldtype that defines it. It will allow the [fieldtype](/extending/fieldtypes) to "augment" the value lazily, allowing the performance overhead to happen only when necessary. An entries field will happily send around a lightweight array of ID strings. It will only try to convert those IDs to entries when it's the right time. No sense in performing unnecessary queries. The `value` method is used to get the augmented value via the fieldtype. It'll be used under the hood when casting to a string or array. The `raw` method will get the un-augmented value. ``` php $markdown = new Value('# Heading', 'content', $markdownFieldtype); $markdown->value(); // '

    Heading

    ' (string) $markdown; // '

    Heading

    ' $markdown->raw(); // '# Heading' ``` ``` php $posts = new Value(['1', '2'], 'posts', $entriesFieldtype); $posts->value(); // [Entry(1), Entry(2)] $posts->raw(); // ['1', '2'] foreach ($posts as $entry) { // Entry(1), Entry(2) } ``` ### Augmentable An augmentable object lets Statamic know that it could potentially have more data available to it than at first glance. For example, a Statamic Asset may have a path and some alt text. However it also has the ability to check the file size, dimensions, timestamp, and so on. All of these things can be lazy-loadable too. You can imagine the wasted overhead involved in checking for all those things for them to never be used. A class may be marked as "augmentable" by implementing the `Augmentable` interface. ``` php use Statamic\Contracts\Data\Augmentable; class Product implements Augmentable { // } ``` Rather than manually implementing all of `Augmentable`'s methods, we provide two traits, depending on how you plan to work. For simple classes, you can use the `HasAugmentedData` trait. ``` php use Statamic\Contracts\Data\Augmentable; use Statamic\Data\HasAugmentedData; class Product implements Augmentable { use HasAugmentedData; public function augmentedArrayData() { return [ 'title' => $this->title, 'price' => $this->price, ]; } } ``` However, since you're providing all the augmented values up front, you aren't really gaining anything. It's a nice way to start providing augmentable classes to templates, though. For more advanced classes, you can use the `HasAugmentedInstance` trait, which lets you define an `Augmented` version of itself (more on that below). ``` php use Statamic\Contracts\Data\Augmentable; use Statamic\Contracts\Data\Augmented; use Statamic\Data\HasAugmentedInstance; class Product implements Augmentable { use HasAugmentedInstance; public function newAugmentedInstance(): Augmented { return new AugmentedProduct($this); } } ``` #### Augmentable Methods | Method | Description | |--------|-------------| | `augmentedValue($key)` | Gets a single augmented value by the key. | | `toAugmentedArray($keys = null)` | Gets an array of augmented values. You can specify which keys or leave it blank for all of them. | | `toAugmentedCollection($keys = null)` | Same as toAugmentedArray, but you get a collection object. | | `toShallowAugmentedArray()` | Gets an array of augmented values, but limited to a specific subset of them. See [shallow augmentation](#shallow-augmentation) | | `toShallowAugmentedCollection()` | Same as toShallowAugmentedArray, but a collection object. | The difference betwen the array and collection methods are that when casting to JSON, the collection will [shallow augment](#shallow-augmentation) nested values. ### Augmented Your objects may have "Augmented" counterparts. These classes allow you define which values will be available once augmented. Take this augmented product class as an example: ``` php use Statamic\Data\AbstractAugmented; class AugmentedProduct extends AbstractAugmented { public function keys() { return [ 'title', 'price', 'perceived_price', ]; } public function perceivedPrice() { return BigMacPrice::calculateFor(User::current(), $this->data->price()); } } ``` When Statamic tries to retrieve a value from this class, it will check in this order: - A camelCased method defined on the class (requesting `perceived_price` will look for a `perceivedPrice` method) - A camelCased method defined on the original class, and wrap it in a Value object. - A value from the original class's data, and wrap it in a Value object. You are required to provide a `keys` method, which lets Statamic know all of the potentially available values, used when it tries to retrieve "all" values. ``` php $augmented->all(); // [ // 'title' => 'My Product', // 'price' => 50, // 'perceived_price' => 55, // ] ``` Let's assume that the `perceivedPrice` method performs an intensive operation, like making an API request or a complex calculation. If we were to request a different value, we would be avoiding the overhead entirely. ``` php $augmented->select(['title', 'price']); // [ // 'title' => 'My Product', // 'price' => 50, // ] ``` As mentioned earlier, the REST API does exactly this. When you request specific fields on the URL, it will be selecting the corresponding augmented values under the hood: ``` php // Request to /api/endpoint/something?fields=one,two $augmented->select(['one', 'two']); ``` ### Shallow Augmentation To prevent potentially enormous or unnecessary amounts of deeply nested data being output in a number of places, we have the concept of shallow augmentation, which just displays a subset of the available augmented values. For example, in the REST API, if you were to request a entry, you'd see all of its fields, but they will only show a limited, single-depth subset of data. ``` yaml id: 1 title: My Post related_posts: - 1 - 2 content: 'The post body content' ``` ``` json { "id": "1", "title": "My Post", "related_posts": [ { "id": "1", "title": "My Post", "api_url": "/api/collections/posts/entries/1", }, { "id": "2", "title": "Another Post", "api_url": "/api/collections/posts/entries/2", } ] } ``` Notice that the `content` and `related_posts` fields do *not* appear in the nested list of entries. You get the basics (there are few more, but kept simple for this example) and enough to make an extra API request if you need it. Also, as this particular example references itself, not only would it add a lot of extra data to the response, it would create an infinite loop! We provide a sensible set of fields to include when shallow augmenting, but if you need to add more fields, you can [override](/extending/repositories#custom-data-classes) the `shallowAugmentedArrayKeys` method on the object. ``` php class CustomEntry extends Entry { public function shallowAugmentedArrayKeys() { return ['id', 'title', 'field_i_must_have', 'api_url']; } } ``` ### Always Augment Relationships to a Query By default, [relationship fieldtypes](/fieldtypes/entries) ([Entries](/fieldtypes/entries), [Terms](/fieldtypes/terms), [Users](/fieldtypes/users), and [Assets](/fieldtypes/assets)) augment differently based on their `max_items` setting: - When `max_items` is **not** `1`, the field augments to a **query builder** that you can chain, filter, and iterate. - When `max_items` **is** `1`, the field short-circuits and augments to the **first result** (e.g. an `Entry`, `Term`, `User`, or `Asset` instance). That shortcut is convenient, but it's inconsistent with the multi-item case and has a subtle side-effect: the automatic "first result" query only considers **published** items. If you need to alter the query (e.g. include drafts or scheduled entries), you can't — you never see the query builder. To force relationship fields to **always** augment to a query builder regardless of `max_items`, enable the `always_augment_to_query` option in `config/statamic/system.php`: ``` php // config/statamic/system.php 'always_augment_to_query' => true, ``` :::hint **This is opt-in and will change your templates.** With it enabled, a `max_items: 1` field no longer returns a single object — it returns a query builder. That means shorthand like `{{ product:title }}` stops working and you must loop (or call `.first()`) explicitly. ::: ::tabs ::tab antlers ```antlers {{# Before: max_items: 1 augments to the related entry #}} {{ product:title }} {{# After: max_items: 1 augments to a query builder #}} {{ product }} {{ title }} {{ /product }} ``` ::tab blade ```blade {{-- Before: $product is an Entry --}} {{ $product->title }} {{-- After: $product is a query builder --}} @foreach ($product as $item) {{ $item->title }} @endforeach ``` :: The upside is that you can now modify the query anywhere a relationship field is used — apply scopes, include drafts, change ordering, etc. — using the same fluent API you'd use on a collection query. ================================================ FILE: content/collections/pages/backend-apis.md ================================================ --- id: 9ca16d8c-a5cf-4fdb-8252-898626e2390b title: 'Backend & APIs' blueprint: link redirect: url: '@child' status: 301 --- ================================================ FILE: content/collections/pages/bard-v1-to-v2.md ================================================ --- id: dbad308e-fdae-42ed-9168-75521bc5d5fe blueprint: page title: 'Upgrade from Bard v1 to v2' intro: 'A guide for upgrading from Bard v1 to v2.' template: page --- ## Overview A new version of Bard (and Tiptap, the library that Bard is built on) was introduced in Statamic 3.4. There is nothing you need to do to upgrade Bard or Tiptap since they're included in the Statamic upgrade, however if you have any custom extensions, you'll need to make some adjustments. ## JavaScript Extensions Extensions no longer need to use our wrappers. You can now use regular Tiptap extensions. Here's how you'd import and bootstrap the extension into Statamic. ```js import AwesomeExtension from './AwesomeExtension'; // [tl!--] import { AwesomeExtension } from './AwesomeExtension'; // [tl!++] Statamic.$bard.addExtension(({ mark }) => mark(new AwesomeExtension)); // [tl!--] Statamic.$bard.addExtension(() => AwesomeExtension); // [tl!++] ``` Here's just the "container" of your extension (so you don't get overwhelmed by a large diff). ```js const { Mark } = Statamic.$bard.tiptap.core; // [tl!++] export default class AwesomeExtension { // [tl!--] export const AwesomeExtension = Mark.create({ // [tl!++] // ... } // [tl!--] }) // [tl!++] ``` And here are the various inner parts of the extension. Starting with name now being a property, and should be camelCased: ```js name() { // [tl! --:start] return 'awesome_extension' } // [tl! --:end] name: 'awesomeExtension' // [tl! ++] ``` Schema is split into parseHTML and renderHTML: ```js schema() { // [tl! --:start] return { parseDOM: [ { style: 'color: red; font-family: cursive' } ], toDOM: () => ['span', { style: 'color: red; font-family: cursive' }, 0], } } // [tl! --:end] parseHTML() { // [tl! ++:start] return [ { style: 'color: red; font-family: cursive' } ]; }, renderHTML() { return ['span', {style: 'color: red; font-family: cursive'}, 0]; } // [tl! ++:end] ``` All commands are explicitly added by name: ```js commands({ type, toggleMark }) {// [tl! --:start] return () => toggleMark(type) }// [tl! --:end] addCommands() {// [tl! ++:start] return { toggleAwesome: () => ({ commands }) => { return commands.toggleMark(this.type); } } }// [tl! ++:end] ``` ## Buttons Buttons can largely remain the same. You need to use the new camelCase name, and use a function to call your command manually. In this example we'll use the `toggleAwesome` command defined above. ```js Statamic.$bard.buttons((buttons, button) => { return button({ name: 'awesome_extension', // [tl! --] name: 'awesomeExtension', // [tl! ++] text: __('Awesome'), command: 'fancy', // [tl! --] command: (editor) => editor.chain().focus().toggleAwesome().run() // [tl! ++] }); }); ``` ## PHP Extensions On the PHP side, we no longer use the `prosemirror-to-html` package. We now use the more official `tiptap-php` package. The classes have changed a little. ```php 'span', 'attrs' => [ 'style' => 'color: red; font-family: cursive' ] ] // [tl!--:end] 'span', // [tl!++:start] HTML::mergeAttributes([ 'style' => 'color: red; font-family: cursive' ], $attributes), 0 // [tl!++:end] ]; } } ``` To add or replace extensions, there is no longer a difference between marks and nodes. You now specify the name and pass the extension. ```php Augmentor::addMark(Awesome::class); // [tl!--] Augmentor::addExtension('awesome_extension', new Awesome); // [tl!++] Augmentor::replaceMark(DefaultBold::class, CustomBold::class); // [tl!--] Augmentor::replaceExtension('bold', new CustomBold); // [tl!++] ``` ================================================ FILE: content/collections/pages/blade-form-fields.md ================================================ --- id: e8504150-db55-4986-b567-19c046cc03de blueprint: page title: 'Blade Form Field Templates' intro: 'By default, [Pre-rendered Field](/tags/form-create#pre-rendered-field-html) templates are implemented in Antlers. If you prefer to use Blade, you may use the following snippets as a starting point in your project.' --- ## Publishing Existing Templates You can publish the existing field templates by running `php artisan vendor:publish --tag=statamic-forms`. It will expose editable templates snippets in your `views/vendor/statamic/forms/fields` directory that will be used by each fieldtype. These templates are used when rendering the pre-rendered `field` variable inside a [form](/forms): ```blade @foreach ($fields as $field)
    {!! $field['field'] !!}
    @endforeach ``` ## Assets `resources/views/vendor/statamic/forms/fields/assets.blade.php` ```blade @php $isMultiple = ! isset($max_files) || $max_files !== 1; $fieldName = $handle; if ($isMultiple) { $fieldName .= '[]'; } @endphp ``` ## Checkboxes `resources/views/vendor/statamic/forms/fields/checkboxes.blade.php` ```blade @php $inline = isset($inline) && $inline === true; @endphp @foreach ($options as $option => $label) @unless ($inline)
    @endunless @endforeach ``` ## Default `resources/views/vendor/statamic/forms/fields/default.blade.php` ```blade ``` ## Dictionary `resources/views/vendor/statamic/forms/fields/dictionary.blade.php` ```blade @php $isMultiple = ! isset($max_items) || $max_items !== 1; $inline = isset($inline) && $inline === true; $placeholderText = $placeholder ?? __('Please select...'); $fieldName = $handle; if ($isMultiple) { $fieldName .= '[]'; } @endphp ``` ## Files `resources/views/vendor/statamic/forms/fields/files.blade.php` ```blade @php $isMultiple = ! isset($max_files) || $max_files !== 1; $fieldName = $handle; if ($isMultiple) { $fieldName .= '[]'; } @endphp ``` ## Radio `resources/views/vendor/statamic/forms/fields/radio.blade.php` ```blade @php $inline = isset($inline) && $inline === true; @endphp @foreach ($options as $option => $label) @unless ($inline)
    @endunless @endforeach ``` ## Select `resources/views/vendor/statamic/forms/fields/select.blade.php` ```blade @php $isMultiple = isset($multiple) && $multiple == true; $inline = isset($inline) && $inline === true; $placeholderText = $placeholder ?? __('Please select...'); $fieldName = $handle; if ($isMultiple) { $fieldName .= '[]'; } @endphp ``` ## Text `resources/views/vendor/statamic/forms/fields/text.blade.php` ```blade ``` ## Textarea `resources/views/vendor/statamic/forms/fields/textarea.blade.php` ```blade ``` ## Toggle `resources/views/vendor/statamic/forms/fields/toggle.blade.php` ```blade ``` ================================================ FILE: content/collections/pages/blade.md ================================================ --- id: c7816387-ebc4-4204-b5f2-8e7073a4db8b blueprint: page title: 'Blade Templates' intro: '[Antlers](/antlers) is not _always_ the best template engine for the job. If you''re using Statamic as a headless CMS or want to share views with a Laravel application already using [Blade](https://laravel.com/docs/blade) or another engine, you can do that.' --- ## Overview While Statamic's [Antlers](/antlers) template language is powerful, tightly integrated, and simple to learn, it's not the only way to build your frontend views. Antlers combines the responsibilities of Blade Templates _and_ [Controllers](/controllers) all at once. If you choose to **not** use Antlers, you _may_ need to create controllers and routes to fetch content and map them to templates depending on what you're doing. Want to write Antlers in your Blade templates? That's also possible by using the [@antlers](#writing-pure-antlers-in-blade) Blade directive. ## How to render a template with Blade Instead of naming your views `myview.antlers.html`, use `myview.blade.php` extension. ## View data You will have access to the same data as you would in Antlers views. ### Current page The current page's data will be available in a `$page` variable, and you can access its values using a syntax similar to Eloquent models. ``` yaml --- title: My First Breakdance Competition moves: - Toprock - 6-step - Windmill - L-kick - Headspin --- I did not win but I did have a good time. ``` ``` blade

    {{ $page->title }}

    First I did @foreach ($page->moves as $move) {{ $move }}, then I did @endforeach and it was sick.

    {{ $page->content }} ``` :::tip Antlers outputs **unescaped** values by default, while `{{ $content }}` in Blade will be escaped. If you need to output unescaped HTML, use `{!! $content !!}` ::: :::tip When on a custom route, the `$page` variable **won't** be available in your view. ::: ### Globals There is a variable for each global set, and its fields can be accessed using the same Eloquent style syntax. ```yaml # content/globals/settings.yaml data: site_name: Rad City ``` ```blade {{ $settings->site_name }} ``` ### System variables Top level [system variables](/variables#system-variables) like, `environment`, `logged_in`, etc will be available as dedicated variables. ```blade {{ $environment }} @if ($logged_in) ... @endif ``` ### Relationships / Queries Some fieldtypes (e.g. `entries`) will supply their data as query builders. These will work similar to Eloquent models, too. If you use property access, it will resolve the query builder and get the items. ```blade @foreach ($page->related_posts as $post) {{ $post->title }} @endforeach ``` If you use a method, it will give you a query builder and allow you to chain clauses on it. ```blade @foreach ($page->related_posts()->where('title', 'like', '%awesome%')->get() as $post) {{ $post->title }} @endforeach ``` ### Loop variables {#loop-variables} The helper variables Antlers exposes inside loops — `first`, `last`, `count`, `index`, `total_results` — are an Antlers feature and are **not** available in Blade. Instead, use Laravel's built-in [`$loop` variable](https://laravel.com/docs/blade#the-loop-variable), which is available inside every `@foreach` and gives you the same information (and more). | Antlers | Blade equivalent | | --- | --- | | `{{ first }}` | `$loop->first` | | `{{ last }}` | `$loop->last` | | `{{ count }}` | `$loop->iteration` | | `{{ index }}` | `$loop->index` | | `{{ total_results }}` | `$loop->count` | ```blade
    • $loop->first])> {{ $loop->iteration }} of {{ $loop->count }} — {{ $title }} @if ($loop->last) (that's all, folks!) @endif
    ``` For nested loops, `$loop->parent` gives you access to the outer loop's variable. See the [Laravel docs](https://laravel.com/docs/blade#the-loop-variable) for the full list of properties. ### Cascade directive When using blade components or rendering views loaded by non-Statamic routes/controllers, the cascade data will be not available by default. In these situations you can use the `@cascade` directive to populate the current scope with cascade data. It works in exactly the same way as the `@props` directive used in blade components, with the ability to require certain values and provide default fallback values: ```blade @cascade([ 'site', // Will throw an exception if missing from the cascade 'my_global', 'page' => null, // Will use fallback if missing from the cascade 'live_preview' => false, ]) {{ $site->locale }} {{ $page?->title }} ``` It is also possible to populate the current scope with all cascade data if needed: ```blade @cascade ``` ## Writing pure Antlers in Blade 🆕 By using the `@antlers` and `@endantlers` Blade directive pair you can write pure Antlers in your Blade templates. ```antlers @antlers {{ collection:articles }} {{ title }} {{ /collection:articles }} @endantlers ``` Under the hood, this is syntactic sugar for creating an Antlers partial and does an on-the-fly `@include('antlers_file_name_here')` for you. This means that variables created _inside_ the Antlers will not be available _outside_ of the `@antlers` directive. ## Using Antlers Blade components Despite the name, Antlers Blade Components are a Blade-only feature that allows you to use existing tags inside your Blade templates using a custom tag syntax. For example, you can gather all entries from a "pages" collection using the [collection](/tags/collection) tag like so: ```blade {{ $title }} ``` In the previous example, you may have noticed the `s:` prefix. To use Antlers Blade Components we must prefix the tag name with either `s:` or `statamic:` (`s-` and `statamic-` also work). We can pass multiple parameters to the tag like so: ```blade {{ $title }} ``` We can also pass dynamic values to parameters: ```blade @php $collection = 'pages'; @endphp {{ $title }} ``` ### Antlers Blade components and partials Partials also work with Antlers Blade components, and are intended to be used when you'd like to include an Antlers partial inside your Blade template: ```blade The header content. Default slot content. ``` :::tip If you are going all-in on Blade for a new project, you should consider sticking to Blade features such as `@include` or Blade components instead of reaching for the `partial` tag. ::: ## Using fluent tags with Blade You can use [Tags](/tags) in Blade templates with a Laravel-style fluent syntax. Instantiate your tag with the `Statamic::tag()` method and chain parameters as needed. ``` blade @foreach(Statamic::tag('collection:pages')->limit(3) as $page)
  • {{ $page->title }}
  • @endforeach ``` ``` • Home • Gallery • Contact ``` :::tip When using multi-word parameters, like `query_scope`, you must use the camelCased version (`queryScope`). ::: ### Using explicit parameter setters If you need to set a parameter containing a colon (ie. a [filter](/tags/collection#filtering) param), you can use the dedicated `param()` setter method: ```php Statamic::tag('collection:pages')->param('title:contains', 'pizza') ``` Or even set multiple parameters at once using the plural `params()` method: ```php Statamic::tag('collection:pages')->params([ 'title:contains' => 'pizza', 'description:contains' => 'lasagna', ]) ``` ### Passing contextual data You can pass in contextual data to the tag using the `context($data)` method: ```php Statamic::tag('collection:pages')->context($context) ``` ### Fetching the output When you loop over a tag or cast it to a string, it will automatically fetch the result for you. In some cases, you may want to explicitly fetch the output. You can do that with the `fetch` method. ```blade @php($output = Statamic::tag('collection:pages')->fetch()) ``` ### Pagination For tags that provide pagination, you can `fetch` the tag's output in a variable, then output the results and links separately: ```blade @php($tag = Statamic::tag('collection:pages')->paginate(2)->as('pages')->fetch()) @foreach($tag['pages'] as $page)
  • {{ $page->title }}
  • @endforeach {{ $tag['paginate']['auto_links'] }} ``` ### Directive {#tag-directive} You may prefer to use an alternate syntax, available via a `@tags` Blade directive. Passing a string will give you the camelCased version of the tag as a variable: ```blade @tags('collection:blog') @foreach($collectionBlog as $post) ... @endforeach ``` Passing an array of tags can provide multiple variables: ```blade @tags(['collection:blog', 'collection:items']) @foreach($collectionBlog as $post) ... @endforeach @foreach($collectionItems as $item) ... @endforeach ``` You may also pass an array of tags, and parameters, with variable names as the keys: ```blade @tags([ 'posts' => ['collection:blog' => ['limit' => 5]], 'items' => ['collection:items' => ['limit' => 5]], ]) @foreach($posts as $post) ... @endforeach @foreach($items as $item) ... @endforeach ``` ## Using modifiers with Blade You can also use [Modifiers](/modifiers) in Blade templates with a Laravel-style fluent syntax. Wrap your value with the `Statamic::modify()` method and chain modifiers as needed. The value will get passed along in sequence like it does in Antlers. Any parameters should be specified like regular PHP parameters. If you use a modifier that can take more than one parameter, pass those in as an array. ``` blade {{ Statamic::modify($content)->striptags()->backspace(1)->ensureRight('!!!') }} {{ Statamic::modify($content)->stripTags()->safeTruncate([42, '...']) }} ``` ``` THIS IS THE FIRST POST, HOW EXCITING!!! I wanted to say more but got cut off... ``` :::tip When using multi-word modifiers, like `ensure_right`, you must use the camelCased version (`ensureRight`). ::: If you're using a lot of modifiers in your Blade template, you can also include the `modify` helper function in your template: ```blade @php use function Statamic\View\Blade\{modify}; @endphp {{ modify('test')->stripTags()->backspace(1)->ensureRight('!!!') }} {{ modify('test')->stripTags()->safeTruncate([42, '...']) }} ``` ## Conditional logic and values Depending on where you got a value from, it may be wrapped in a class like `Value`. These can lead to unexpected results in conditional logic if they are not handled correctly (i.e., calling `->value()` in the condition). If you'd prefer to not think about that, you can include the `value` helper function into your template, which will take care of this for you: ```blade @php use function Statamic\View\Blade\{value}; @endphp @if (value($theVariableName)) ... @endif ``` The `value` helper function will handle the following scenarios for you: * `Statamic\Fields\Value` objects (calls `->value()`) * `Statamic\Fields\Values` objects (calls `->all()`) * `Statamic\Tags\FluentTag` objects (calls `->fetch()`) * `Statamic\Modifiers\Modify` objects (calls `->fetch()`) ## Layouts When Statamic attempts to render a URL (eg. an entry), two views are combined. A template gets injected into a layout's `template_content` variable. When the _template_ is **not** an Antlers view, this rule doesn't apply. The layout is ignored, allowing you to use `@extends` the way you would expect. ``` blade {{-- mytemplate.blade.php --}} @extends('layout') @section('body') The body content @endsection ``` ``` blade {{-- mylayout.blade.php --}} @yield('body') ``` ```html The body content ``` This rule only applies to the _template_. You're free to use a `.antlers.html` template and a `.blade.php` layout. If you want to do this, the contents of the template will be available as in the `template_content` variable instead of `yield`. ``` {{# mytemplate.antlers.html #}} The template content ``` ``` blade {{-- mylayout.blade.php --}} {!! $template_content !!} ``` ```html The template contents ``` ### Passing context into components If you are using Blade components for your layout rather than Blade directives, you might want to pass the view context into your layout for access by child components. You can do so with the special `$__data` variable in the layout root, and the `@aware` directive in the child. Here's how: First, add a `context` prop to your layout component. ```blade {{-- resources/views/components/layout.blade.php --}} @props(['context']) {{-- whatever you want to put in here... --}} ``` Then, merge the `context` prop with the parent data in your Layout component's `data` method. ```php context = $context; } public function data() { return array_merge(parent::data(), $this->context); } /** * Get the view / contents that represent the component. * * @return \Illuminate\Contracts\View\View|\Closure|string */ public function render() { return view('components.layout'); } } ``` Next, pass in the magic `$__data` variable from your template to your layout. ```blade {{-- resources/views/default.blade.php --}} ``` Last, use the `@aware` directive in any child component of your layout to access the variables from the cascade within your component. ```blade {{-- resources/views/components/blade.php --}} @aware(['page'])
    {{ $page->hero_headline }}
    ``` ## Routes and controllers If you choose to take a more "traditional" Laravel application approach to building your Statamic site, you can use routes and controllers much the same way you might with Eloquent models instead of Statamic's native collection routing and data cascade. Here's an example: ### The routes ```php use App\Http\Controllers\BlogController; Route::get('/blog', [BlogController::class, 'index']); Route::get('/blog/{slug}', [BlogController::class, 'show']); ``` ### The controller ```php where('collection', 'blog') ->take(10) ->get(); return view('blog.index', ['entries' => $entries]); } public function show($slug) { $entry = Entry::query() ->where('collection', 'blog') ->where('slug', $slug) ->first(); return view('blog.show', ['entry' => $entry]); } } ``` ### The index view ```blade

    The Blog

    @foreach($entries as $entry)

    {!! $entry->title !!}

    {!! $entry->date->format('Y-m-d') !!}
    @endforeach
    ``` ### The show view ```blade

    {!! $title !!}

    {!! $entry->date->format('Y-m-d') !!}
    {!! $entry->content !!}
    ``` ================================================ FILE: content/collections/pages/blink-cache.md ================================================ --- title: 'Blink Cache' template: page intro: 'The Blink Cache allows you to cache expensive operations for the life of a single request.' id: 73010f4f-bb62-4feb-a6ff-88031f488db8 --- ## Basic Usage ```php use Statamic\Facades\Blink; Blink::put('key', 'value'); $value = Blink::get('key'); // value ``` A more powerful method is `once`. It will run a function once and cache the value. ``` php $expensiveFunction = function() { return rand(); }); $blink->once('random', $expensiveFunction); // returns random number $blink->once('random', $expensiveFunction); // returns the same number ``` Under the hood, it uses [Spatie's Blink package](https://github.com/spatie/blink). You are able to use any of the methods mentioned there. ## Stores The Blink cache is organized into different "stores", each of which are a separate Blink instances. You can get your own Blink cache by using the `store` method which could be useful to prevent conflicts with other packages, or if you plan to use the `flush` methods. Once you have the instance, you can call the Blink methods on it. ```php Blink::store('mystore')->put(...); ``` Without explicitly calling the `store` method, any methods will be targeting the "default" store shared across the application. ================================================ FILE: content/collections/pages/blueprints.md ================================================ --- id: 54548616-fd6d-44a3-a379-bdf71c492c63 blueprint: page title: Blueprints intro: 'Blueprints are a key component of the content modeling process. Inside a blueprint you define your fields, which field types they''ll implement, group them into sections if you desire, and define conditions controlling their visibility. The control panel uses blueprints to render publish forms so you can manage content.' related_entries: - 2940c834-7062-47a1-957c-88a69e790cbb - 9a1d8b88-c600-46f2-8727-1deb56f2e87a --- ## Overview Think of blueprints as stencils for your content. They control what [fields](/fields) users get to work with when publishing content, as well as the schema of the data developers will be tapping into to build the [front-end](/frontend) of your site. Each blueprint belongs to an item: - You can define multiple Blueprints for collections, and each entry will have the opportunity to choose from one of them. - Same goes for taxonomies and their terms. - Global sets, Asset containers, and Forms each get their own Blueprint. - Users all share a Blueprint.
    The Statamic blueprint configuration screen The Statamic blueprint configuration screen
    A glimpse at configuring a blueprint.
    ## Creating Blueprints There are 3 ways to create blueprints: - In the respective areas of the control panel. For instance, the collections area will let you define its blueprints. The forms area will let you define its blueprints, and so on. - In the **Blueprints** area of the control panel. This page serves as a hub to jump over to managing blueprints in various areas. - Creating a YAML file in the appropriate place within `resources/blueprints/`. More on that in a moment. Once created, you can begin to define fields and the sections that hold them. If you have more than one section, each becomes a tab in the publish form. ## Directory Structure Whether you manually create your blueprint's YAML file, or use the control panel, they will all end up as YAML files in the `resources/blueprints` directory.
    Statamic Blueprints Folder Structure
    Here's how Blueprints are organized in the filesystem.
    ``` files theme:serendipity-light resources/ blueprints/ collections/ blog/ basic_post.yaml art_directed_post.yaml taxonomies/ tags/ tag.yaml globals/ global.yaml company.yaml assets/ main.yaml forms/ contact.yaml user.yaml ``` Collections and Taxonomies have their available blueprints organized in subdirectories named after their collections. When you create an entry or term, you will be able to choose which blueprint to use (if there are multiple). Globals, Asset Containers, and Forms can only have one blueprint per item, so they are organized into their own subdirectories, where each YAML file is the handle of the item. All users will share the same blueprint, and it hangs out in the root of the directory. ### Customizing the Path You can change where blueprints are stored by setting the `blueprints_path` option in `config/statamic/system.php`: ```php 'blueprints_path' => resource_path('blueprints'), ``` You may also provide an array to use different paths for specific blueprint types. This is especially useful for form blueprints when you want to keep them alongside other editable content — for example, if you exclude the `content/` directory from git and don't want form blueprints tracked: ```php 'blueprints_path' => [ 'default' => resource_path('blueprints'), 'forms' => base_path('content/forms/blueprints'), ], ``` The `default` key is required when using an array — it's used for any blueprint types you haven't explicitly specified. When a type-specific path is set, the type is _not_ appended to the path. Using the example above, a contact form blueprint lives at `content/forms/blueprints/contact.yaml`, not `content/forms/blueprints/forms/contact.yaml`. ## Conditional Fields It’s possible to have fields be displayed only under certain conditions. For example, you may only want to show a caption field if an asset field has an image selected, or a whole block of fields if a toggle switch is enabled.
    Statamic conditional field rule builder Statamic conditional field rule builder
    The conditional field rule builder
    To learn what's possible and how to implement the various rules, head over to the article on [conditional fields](/conditional-fields). ## YAML Structure At its most basic, a blueprint has an array of sections. ``` yaml sections: [] ``` A section has a handle, a display name, and an array of fields: ``` yaml sections: main: display: Main fields: - handle: content type: markdown meta: display: SEO Metadata fields: - handle: meta_title type: text - handle: meta_description type: textarea ``` :::tip Blueprint fields are **indexed sequentially** instead of keyed by handle. This format allows maximum flexibility: you can reference fields from other blueprints one or more times, override their settings inline, and even reference existing fields for [Bard](/fieldtypes/bard), [Replicator](/fieldtypes/replicator), and [Grid](/fieldtypes/grid) sets. ::: ## Reusable Fields A section's fields can be comprised of references to fields in fieldsets (so you can reuse fields) or inline field definitions. ### Field References You will likely want to pre-configure reusable fields to pull into your blueprints. For example, you might have a rich text field configured with all your favorite buttons, which you've called `content` and stored in the `common` fieldset. You can pull it into your blueprint like so: ``` yaml fields: - handle: my_content_field field: common.content - handle: another_content_field field: common.content ``` This way, you are free to reuse the same field as many times as you like. Update the field in the fieldset and it will be reflected across all your blueprints. ### Customizing Fields You may customize a referenced field by adding a `config` array. Any keys found in this will _override_ whatever was defined in your fieldset. ``` yaml fields: - handle: my_content_field field: common.content config: display: My Content Field validate: required|max:200 ``` Here, the `display` and `validate` would replace whatever was defined in the fieldset. **Note:** This only applies to referenced fields. For inline fields, you can just set everything right there. ### Importing Fieldsets Fieldsets serve to create reusable sets of fields. You may import an entire fieldset at any point by using the `import` key, for example: ``` yaml # blueprint fields: - import: survey prefix: favorite_ - import: survey prefix: least_favorite_ ``` ``` yaml # the survey.yaml fieldset fields: - handle: food type: text - handle: food_reason type: textarea ``` Doing the above would result in a blueprint like this: ``` yaml fields: - handle: favorite_food field: type: text - handle: favorite_food_reason field: type: textarea - handle: least_favorite_food field: type: text - handle: least_favorite_food_reason field: type: textarea ``` It would bring every field inline and prefix each field's handle appropriately. If you omit the `prefix` you won't be able to import them more than once at the same level because they would have the same handle and overwrite each other. If the fieldset you're importing has [its own sections](/fieldsets#sections), you can control how they're rendered with `section_behavior`. Set it to `preserve` (the default) to keep the fieldset's sections intact in the publish form, or `flatten` to merge everything into the current section. ```yaml fields: - import: seo section_behavior: flatten ``` ## Validation Fields can have various validation rules applied to them, enforcing the need for content creators to fill them out in a specific way before saving or publishing. While configuring a field, switch to the **Validation** tab where you can choose from [any built in Laravel rule](https://laravel.com/docs/13.x/validation#available-validation-rules). On top of any Laravel validation rules, there are some Statamic-specific goodies (like usage with conditional fields, Grids, Bards, or Replicators) that are explained on our [dedicated validation documentation](/validation). ## Grid fieldtype The [Grid fieldtype](/fieldtypes/grid) lets you define a set of sub-fields, which it will allow you to repeat as many times as you like. You should define its fields using the blueprint field syntax. This will allow you to reference other fields and/or import entire fieldsets.
    An example grid field An example grid field
    This is an example Grid field.
    ``` yaml links: type: grid fields: - handle: url field: links.url - handle: text field: links.text - handle: external field: links.external ``` ## Unlisted fields While [conditional fields](#conditional-fields) allow you to control field visibility on the publish form, you may also customize column visibility on **entry listings** in the control panel. ```yaml listable: false ``` This hides the field column from entry listings, which can be useful for toggle fields etc, which may never make sense in the context of an entry listing. ```yaml listable: hidden ``` This will hide the field from entry listings by default, but still allows a user to toggle visibility using the column selector, and save those column preferences for his/her preferred workflow. ================================================ FILE: content/collections/pages/build-a-fieldtype.md ================================================ --- id: 83786f60-def6-11e9-aaef-0800200c9a66 blueprint: page title: 'Build a Fieldtype' updated_by: 3a60f79d-8381-4def-a970-5df62f0f5d56 updated_at: 1568643872 intro: "Fieldtypes determine the user interface and storage format for your [fields](/fields). Statamic includes 40+ fieldtypes to help you tailor the perfect intuitive experience for your authors, but there's always room for _one more_." --- ## Creating Fieldtypes have a PHP component and JS component. You can use a command to generate both pieces: ``` shell php please make:fieldtype Uppercase ``` ``` files theme:serendipity-light app/ Fieldtypes/ Uppercase.php resources/ js/ components/ fieldtypes/ Uppercase.vue ``` If you haven't already [set up Vite](/control-panel/css-javascript) for the Control Panel, the command will do it for you. ## Registering Statamic will automatically register any fieldtype classes in the `App\Fieldtypes` namespace. However, if you wish to store them elsewhere, you'll need to manually register it by calling the static `register` method on your fieldtype's class: ``` php public function boot() { \App\SomewhereElse\Uppercase::register(); } ``` ## PHP Class The PHP class can be very barebones. At the most basic level, it just needs to exist in order to let Statamic know about it. ```php ... '; // or function icon() { return file_get_contents(resource_path('svg/left_shark.svg')); } } ``` ### Categories When using the blueprint builder inside the Control Panel, your fieldtype will be listed under the `special` category by default. To move your fieldtype into a different category, define the `$categories` property on your class: ```php [ 'display' => 'Mode', 'instructions' => 'Choose which mode you want to use', 'type' => 'select', 'default' => 'regular', 'options' => [ 'regular' => __('Regular'), 'enhanced' => __('Enhanced'), ], 'width' => 50 ], 'secret_agent_features' => [ 'display' => 'Enable super secret agent features', 'instructions' => 'Can you even handle these features?', 'type' => 'toggle', 'default' => false, 'width' => 50 ], ]; } ``` The configuration values can be accessed in the Vue component using the `config` property. ``` js return this.config.mode; // regular ``` #### Options | Key | Definition | |------------------|------------------------------------------------------------------| | **display** | The field's display label | | **instructions** | Text shown underneath the display label. Supports Markdown. | | **type** | Name of the fieldtype used to manage the config option. | | **default** | An optional default value. | | **width** | The field's width. | | ***other*** | Some fieldtypes have additional configuration options available. | :::tip A little code diving will reveal all the possible config options for each field type. Look for the `configFieldItems()` method in each class here: ::: ## Vue Component The Vue component is responsible for the view and data binding. It's what your user will be interacting with. The `make:fieldtype` command would have generated a Vue component into `resources/js/components/fieldtypes/Uppercase.vue`. You'll need to register this Vue component in your JS entry file (`resources/js/cp.js`): ``` js import UppercaseFieldtype from './components/fieldtypes/Uppercase.vue'; Statamic.booting(() => { // Should be named [snake_case_handle]-fieldtype Statamic.$components.register('uppercase-fieldtype', UppercaseFieldtype); }); ``` Your component should use our `Fieldtype` composable for defining props & emits, updating the field value and accessing meta. ``` vue ``` Other than that, your component can do whatever you like! ### Example For this example we will create an input field with a button to make the text uppercase:
    An example fieldtype with a button to make the text uppercase
    Follow along and you could make this!
    ``` vue ``` #### What's happening? 1. The `Fieldtype` composable is providing the `emits` and `props` we need to define, as well as the `expose, update` and `updateDebounced` methods. 2. When you type into the text field, an `update` method is called which emits an event. Statamic listens to that event and updates the `value` prop. Those are the two requirements satisfied. ✅ In addition to that, when the button is clicked, we're converting the string to uppercase and calling `update` in our function. ### Accessing other fields If you find yourself needing to access other form field values, configs, etc., you can reach into the publish form store from within your Vue component: ```js import { injectPublishContext } from '@statamic/cms/ui'; const { values } = injectPublishContext(); // Do what you need to with values console.log(values.value.title) ``` ## Processing You may need to modify the data going to and from the browser. * The `preProcess` method allows you to modify the original value into what the Vue component requires. * The `process` method does the opposite. It takes the Vue component's value and allows you to modify it for what gets saved. For example, the YAML fieldtype stores its value in content as an array but the field needs it as a string in order for it to be editable: ```php public function preProcess($value) { return YAML::dump($value); // dump a yaml string from an array } ``` In the other direction, it takes the YAML string and needs to convert it back to an array when saving: ```php public function process($value) { return YAML::parse($value); // parses a yaml string into an array } ``` _(These snippets are simplified for example purposes.)_ ## Meta Data Fieldtypes can preload additional "meta" data from PHP into JavaScript. This can be anything you want, from settings to eager loaded data. ``` php public function preload() { return ['foo' => 'bar']; } ``` This can be accessed in the Vue component using the `meta` prop. ``` js return props.meta; // { foo: bar } ``` If you have a need to update this meta data on the _JavaScript side_, use the `updateMeta` method. This will persist the value back to Vuex store and communicate the update to the appropriate places. ``` js const props = defineProps(Fieldtype.props); const { updateMeta } = Fieldtype.use(emit, props); updateMeta({ foo: 'baz' }); props.meta; // { foo: 'baz' } ``` ### Example use cases Here are some reasons why you might want to use this feature: - The assets and relationship fieldtypes only store IDs, so they will fetch item data using AJAX requests. If you have many of these fields in one form, you'd have a bunch of AJAX requests fire off when the page loads. Preload the item data to avoid the initial AJAX requests. - Grid, Bard, and Replicator fields all preload values for what a new row/set contains, plus the recursive meta values of any nested fields. ## Replicator Preview When [Replicator](/fieldtypes/replicator) (or [Bard](/fieldtypes/bard)) sets are collapsed, Statamic will display a preview of the values within it. By default, Statamic will do its best to display your fields value. However, if you have a value more complex than a simple string or array, you may want to customize it. You may customize the preview text by calling `defineReplicatorPreview` from your Vue component. For example: ``` js const { defineReplicatorPreview } = Fieldtype.use(emit, props); defineReplicatorPreview(() => props.value.join('+')); ``` :::tip This _does_ support returning an HTML string so you could display image tags for a thumbnail, etc. Just be aware of the limited space. ::: ## Index fieldtypes In listings (collection indexes in the Control Panel, for example), string values will be displayed as a truncated string and arrays will be displayed as JSON. You can adjust the value before it gets sent to the listing with the `preProcessIndex` method: ``` php public function preProcessIndex($value) { return str_repeat('*', strlen($value)); } ``` If you need extra control or functionality, fieldtypes may have an additional "index" Vue component. ``` js import UppercaseIndexFieldtype from './UppercaseIndexFieldtype.vue'; // Should be named [snake_case_handle]-fieldtype-index Statamic.$components.register('toggle_password-fieldtype-index', UppercaseIndexFieldtype); ``` ``` vue ``` The `IndexFieldtype` composable will provide you with a `value` prop so you can display it however you'd like. Continuing our example above, we will calculate how many characters of the given string are uppercase. ## Augmentation By default, a fieldtype will not perform any augmentation. It will just return the value as-is. You can customize how it gets augmented with an augment method: ``` php public function augment($value) { return strtoupper($value); } ``` [Read more about augmentation](/extending/augmentation) ## Updating References If your fieldtype stores references to assets or taxonomy terms — either directly or nested inside sub-fields — you'll want to keep those references in sync when an asset is renamed, moved, or deleted, or when a term is renamed or deleted. Otherwise you end up with broken references pointing at stale URLs or IDs. Statamic handles this for all built-in fieldtypes automatically. For custom fieldtypes you can opt in by using the `UpdatesReferences` trait and overriding one or more of its methods. ```php use Statamic\Fields\Fieldtype; use Statamic\Fieldtypes\UpdatesReferences; class MyFieldtype extends Fieldtype { use UpdatesReferences; } ``` The trait provides three no-op methods you can override, depending on what kind of data your fieldtype stores: | Method | Purpose | |--------|---------| | `replaceAssetReferences($data, $newValue, $oldValue, $container)` | Replace direct asset references (URLs or IDs). | | `replaceTermReferences($data, $newValue, $oldValue, $taxonomy)` | Replace direct term references. | | `iterateReferenceFields($data, NestedFieldUpdater $updater)` | Traverse nested sub-fields so Statamic can recurse into them. | It also gives you three helper methods for the common cases: `replaceValue()`, `replaceValuesInArray()`, and `replaceStatamicUrls()`. ### Asset references If your fieldtype stores a single asset reference, override `replaceAssetReferences` and use the `replaceValue()` helper. Bail early if the container doesn't match your field's configured container. ```php use Statamic\Fields\Fieldtype; use Statamic\Fieldtypes\UpdatesReferences; class MyLinkFieldtype extends Fieldtype { use UpdatesReferences; public function replaceAssetReferences($data, ?string $newValue, string $oldValue, string $container) { if ($this->config('container') !== $container) { return $data; } return $this->replaceValue($data, $newValue, $oldValue); } } ``` When `$newValue` is `null`, the asset was deleted — Statamic will remove the reference from your data. ### Term references If your fieldtype stores an array of term references, override `replaceTermReferences` and use `replaceValuesInArray()`. ```php use Statamic\Fields\Fieldtype; use Statamic\Fieldtypes\UpdatesReferences; class MyTagsFieldtype extends Fieldtype { use UpdatesReferences; public function replaceTermReferences($data, ?string $newValue, string $oldValue, string $taxonomy) { return $this->replaceValuesInArray($data, $newValue, $oldValue); } } ``` ### Nested sub-fields If your fieldtype contains nested fields (like Grid, Replicator, or a custom Columns fieldtype), override `iterateReferenceFields` and hand each set of nested fields off to the `NestedFieldUpdater`. Statamic will recurse into them and run the appropriate reference updates against any fieldtypes within that also use the trait. ```php use Statamic\Data\NestedFieldUpdater; use Statamic\Fields\Fields; use Statamic\Fields\Fieldtype; use Statamic\Fieldtypes\UpdatesReferences; class ColumnsFieldtype extends Fieldtype { use UpdatesReferences; public function iterateReferenceFields($data, NestedFieldUpdater $updater): void { if (! is_array($data)) { return; } $fields = new Fields($this->config('fields')); foreach (array_keys($data) as $idx) { $updater->update($fields, "{$idx}."); } } } ``` The second argument to `$updater->update()` is a key prefix used when walking the data array — use it to tell the updater where the nested field's value lives within your data structure. ## Adding config fields to existing fieldtypes Sometimes you may want to add a config field to another fieldtype rather than creating a completely new one. You can do this using the `appendConfigField` or `appendConfigFields` methods on the respective fieldtype. ```php use Statamic\Fieldtypes\Text; // One field... Text::appendConfigField('group', [ 'type' => 'text', 'display' => 'Group', ]); // Multiple fields... Text::appendConfigFields([ 'group' => ['type' => 'text', 'display' => '...',], 'another' => ['type' => 'text', 'display' => '...',], ]); ``` You can also append a config field to _all_ fieldtypes via the `Fieldtype` class: ```php use Statamic\Fields\Fieldtype; Fieldtype::appendConfigField('group', [ 'type' => 'text', 'display' => 'A new group', ]); ``` ================================================ FILE: content/collections/pages/building-a-tag.md ================================================ --- title: Building Tags template: page updated_by: 42bb2659-2277-44da-a5ea-2f1eed146402 updated_at: 1569264076 intro: Ultimately a Tag is nothing more than a PHP method called from an Antlers or Blade template. This common pattern allows non-PHP developers to take advantage of dynamic features in their site easily without writing any code. id: 098cb1c5-94c2-4bc0-add7-9aad39951d67 --- ## Anatomy of a Tag A tag consists of several parts, none of which are named the “thorax”. Let’s break a Tag down: ::tabs ::tab antlers ```antlers {{ acme:foo_bar src="baz" }} ``` - The first part? That’s the tag's handle: `acme` - The second bit is the method it maps to: `fooBar` (the method will be camelCased) - And lastly, a parameter: `src="foo"`. There can be any number of parameters on a Tag. ::tab blade ```blade ``` - The first part after `{{ title }} {{ /entries:listing }} ``` ::tab blade ```blade
    {{ $title }}
    ``` :: Anything in-between your tag pair is available as `$this->content`. Sometimes you’ll want to use it as input, other times manipulate it, and yet another time leave it be. It’s up to you. ## Multiple Tags in one class We use the plural "Tags" for the class, because one class defines multiple tags. They just all use the same handle. Every `public` method in a tag class will become a tag, using the snake_cased version. For example: ``` php ``` :: ### Index A tag without a method will use the `index` method. ``` php public function index() { // It's just one of those days. } ``` Template: ::tabs ::tab antlers ```antlers {{ my_tags }} ``` ::tab blade ```blade ``` :: ### Wildcards A common tag pattern is to have the method be dynamic. For example, the [Collection Tag][collection_tag]'s method is the handle of the collection you want to work with: `collection:blog`. You may add a `wildcard` method to your Tag class to catch these. ``` php public function wildcard($tag) { // ♠️♥️♦️♣️ } ``` then, in your template: ::tabs ::tab antlers ```antlers {{# Replace the * with anything you'd like! Go wild - play your crazy card! #}} {{ tag:* }} {{# In this case, the `$tag` value would be "foo" #}} {{ tag:foo }} ``` ::tab blade ```blade {{-- Replace the * with anything you'd like! Go wild - play your crazy card! --}} {{-- In this case, the `$tag` value would be "foo" --}} ``` :: If you want a tag named literally "wildcard", you can adjust the wildcard method that Statamic will call by updating the `wildcardMethod` property. ```php protected $wildcardMethod = 'missing'; public function wildcard() { // tag:wildcard } public function missing($tag) { // tag:* } ``` :::best-practice You may notice that the `wildcard` method seems very similar to the `__call()` magic method. It is! The `wildcard` method uses `__call` under the hood, but with additional smarts. Be sure to use `wildcard`! ::: ## Generating a tag class You can generate a Tags class with a console command: ``` shell php please make:tag Foo ``` This'll create a class in `app/Tags` which will be automatically registered. To create and register a tag inside an addon instead, check out the [addon docs](/extending/addons#registering-components). ## Tag Handle The first part of the tag will be the tag's "handle". This will be the snake_cased version of the class name by default. In this example, it would be `my_tags`. You can override this by setting a static `$handle` property. ``` php protected static $handle = 'example'; ``` Then, using the example above, `my_tags:x` would now be `example:x` ## Aliases You may choose to create aliases for your tag too. It will then be usable by its handle, or any of its aliases. ``` php protected static $aliases = ['sample']; ``` ## Parameters You may get the values of parameters through the `parameters` property. Any parameters prefixed with a colon will resolve the values from the context automatically. ``` yaml author: john ``` ::tabs ::tab antlers ```antlers {{ mytag greeting="hello" :name="author" do_this="true" do_that="false" limit="5" latitude="6" things="foo|bar" }} ``` ::tab blade ```blade ``` :: ``` php $this->params->get('greeting'); // "hello" $this->params->get('name'); // "john" $this->params->bool('do_this') // true $this->params->bool('do_that') // false $this->params->int('limit') // 5 $this->params->float('latitude') // 6.0 $this->params->explode('things') // ['foo', 'bar'] // Array access also works: $this->params['greeting']; // "hello" // It's a Collection, so all those methods work, too. $this->params->only(['greeting', 'name']); // ['hello', 'john'] ``` For any of these methods, you may provide a second argument as a fallback if the key doesn't exist. ``` php $this->params->get('nope', 'fallback'); // "fallback" ``` You may also provide an array of keys. The first match will be used. ``` php $this->params->get(['salutation', 'greeting']); // "hello" ``` ## Context The context is the array of values being used for rendering the template where your tag happens to be placed. Take this block of templating for example: ::tabs ::tab antlers ```antlers {{ title }} {{ collection:blog }} {{ title }} {{ your_tag }} {{ /collection:blog }} ``` ::tab blade ```blade {{ $title }} {{ $title }} ``` :: The `title` in the first line uses the page context since it's not nested inside any other tag pairs. This would typically be the title of the entry of the URL you're visiting. You can pretend that the entire template is wrapped in a pair of invisible tags. The `collection:blog` tag loops over entries in a collection. The `title` in there will be using the context of the entry in the current iteration of the loop. Likewise, the `your_tag`'s context will be current iteration of the loop. You may get values out of the context similar to [parameters](#parameters): ``` php $this->context->get('title'); $this->context->get('unknown', 'fallback'); $this->context->get(['first_this', 'then_this']); ``` If the item you are retrieving is defined in a Blueprint, it'll be a `Value` object. To avoid you needing to check for Value objects and manually convert them yourself, you may use the `value` and `raw` methods instead: ``` php // If it's a Value object, it'll get the augmented value for you. $this->context->value('something'); // If it's a Value object, it'll get the raw value for you. $this->context->raw('something'); ``` ## Rendering Data Rendering your tag data is a little different depending on whether you intend to have a single tag or a tag pair. Generally, you should try to have a tag that is either always used as a single, or a pair. If you _need_ a tag to work either way, the `$this->isPair` boolean is available to you. ### Single Tags A single tag stands alone by itself and does not have a closing tag. Your method must return a string if you to render something. Within a template, your tag will be replaced with that returned string. ``` php public function method() { return 'hello'; } ``` ::tabs ::tab antlers ```antlers {{ your_tag:method }} ``` ::tab blade ```blade ``` :: ```text hello ``` You may also return a boolean. This is useful if your tag is designed to be used in conditions. ``` php public function method() { return true; } ``` ::tabs ::tab antlers ```antlers {{ if {your_tag:method} }} yup {{ /if }} ``` ::tab blade ```blade @if (Statamic::tag('your_tag:method')->fetch()) yup @endif ``` :: ```text yup ``` If your tag doesn’t return anything, your tag won’t render anything. This can be useful if you need to perform some sort of non-HTML rendering task. For example, the redirect tag doesn’t output any HTML, it just performs a redirect. ### Tag Pairs When using your tag in a pair, you can return an array (or Collection) to be used as the new context. Its variables will be available between your tags. ``` php public function method() { return [ 'tree' => 'maple', 'path' => 'dirt', 'sky' => 'blue' ]; } ``` ::tabs ::tab antlers ```antlers {{ your_tag:method }} {{ tree }} {{ path }} {{ sky }} {{ /your_tag:method }} ``` ::tab blade ```blade {{ $tree }} {{ $path }} {{ $sky }} ``` :: ```text maple dirt blue ``` Returning a multidimensional array will let you loop over the items. ``` php public function method() { return [ [ 'tree' => 'maple', 'path' => 'dirt', 'sky' => 'blue' ], [ 'tree' => 'oak', 'path' => 'asphalt', 'sky' => 'black' ] ]; } ``` ::tabs ::tab antlers ```antlers {{ your_tag:method }} {{ tree }} {{ path }} {{ sky }} {{ /your_tag:method }} ``` ::tab blade ```blade {{ $tree }} {{ $path }} {{ $sky }} ``` :: ```text maple dirt blue oak asphalt black ``` ### Empty Results in Antlers When using Antlers, a `no_results` variable will be automatically created when a tag returns an empty array. ``` php public function method() { return [ ]; } ``` ``` {{ your_tag:method }} {{ if no_results }} No results. {{ else }} Some results. {{ /if }} {{ /your_tag:method }} ``` ``` No results. ``` ### Empty Results in Blade There are many different ways to handle empty results in Blade. When iterating simple tag results without aliasing, there is a special Blade component that takes the place of the `no_results` variable: ``` php public function method() { return [ ]; } ``` ```blade Some results. No results. ``` In all other scenarios, you will need to use other techniques. Some examples are: ```blade {{-- Checking the count. --}} @if (count($results) > 0) Some results. @else No results. @endif {{-- Using forelese --}} @forelse ($results as $value) ... @empty No results. @endforelse ``` Whichever method you choose will depend on the situation and your personal preference. ### Passing along context As mentioned above, the array returned from a tag pair method is what'll be available between the tags. The parent context is not available. This is to prevent variable collision and confusion. If you *want* parent context to be available, you can pass those down manually. ``` php // One at a time return [ 'local' => 'value', 'var' => $this->context->get('var'), ]; ``` ``` php // Merging in multiple return array_merge( $this->context->only('foo', 'bar', 'baz')->all(), ['local' => 'value'] ); ``` ## Considerations for Blade Most tag implementations will work seamlessly whether a user is writing their templates in Antlers or Blade, but there are a few things to keep in mind. ### Conditionally Included Variables and "Falsey-ness" Tag implementations that conditionally inject a variable should consider always including the variable in their output, with a backwards-compatible default. Antlers will treat non-existent variables as `false` in conditions, and skip them entirely in other contexts. Because Blade compiles to PHP, if you do not always include the variable, users of your tag will have to resort to adding `isset` (or similar) checks. If your tag conditionally injects a variable that template authors rely on to change their output, consider adjusting the logic such that the variable is always available, with a backwards-compatible default. An example of this is Statamic's own form tag and the `success` variable. ### Aliased Array Results The Antlers engine will automatically alias array results. Consider the following tag: ```php @foreach ($the_array_name as $value) {{ $value }} @endforeach ``` However, with the current tag implementation, this would not work and template authors would receive an error stating the `$the_array_name` variable doesn't exist. To make this work, we need to add support for the `as` parameter to our tag directly. Luckily, an `aliasedResult` helper method exists to make this easy for us: ```php aliasedResult(['a', 'b', 'c']); // [tl! ++] } } ``` ### Don't Assume Tag Content is Always Antlers If you are interacting with a tag's content and rendering it manually, you should *not* assume that the tag's content is always Antlers. For example, if you have a tag implementation that looks something like this: ```php content); } } ``` consider using the `parse()` method instead, which will take the current templating language into consideration: ```php content); // [tl! --] return $this->parse(); // [tl! ++] } } ``` ### Implementing Custom Behavior for Blade vs. Antlers If you want to change your tags behavior specifically for Blade, you can check if the current tag instance is being rendered within a Blade template like so: ```php isAntlersBladeComponent()) { return 'Hello, Blade!'; } return 'Hello, Antlers!'; } } ``` ## Miscellaneous - `$this->content` - When using a tag pair, this is what's between them. - `$this->isPair` - Boolean for whether a single or tag pair was used. - `$this->tag` - The full tag that was used. - For `{{ ron foo="bar" }}` it would be `ron:index` - For `{{ ron:swanson foo="bar" }}`, this would be `ron:swanson` - For `{{ ron:swanson:breakfast foo="bar" }}`, this would be `ron:swanson:breakfast` - `$this->method` - The tag method that was used. - For `{{ ron foo="bar" }}`, it would `index` - For `{{ ron:swanson foo="bar" }}`, this would be `swanson` - For `{{ ron:swanson:breakfast foo="bar" }}`, this would be `swanson:breakfast` [collection_tag]: /tags/collection ================================================ FILE: content/collections/pages/building-a-widget.md ================================================ --- title: Building Widgets template: page updated_by: 42bb2659-2277-44da-a5ea-2f1eed146402 updated_at: 1569264107 id: 5900c99f-89b9-4ee3-834c-cb1b070146e4 --- ## Generating a widget You can generate a widget with a console command: ```shell php please make:widget LocalWeather ``` This will automagically create a class in `app/Widgets` and a Vue component in `resources/js/components/widgets`. The PHP class is responsible for returning the Vue component and any props: ```php // app/Widgets/LocalWeather.php 'Hello World!']); } } ``` ```blade ``` The `` component requires a `title` prop, along with optional `icon` and `href` props. You also pass an `actions` slot to render content in the top right of the widget. If you'd prefer to create your widget using Blade, simply pass the `--blade` argument to the `make:widget` command. ## Configuring Widgets can be added to the dashboard by modifying the `widgets` array in the `config/statamic/cp.php` file. ``` php // config/statamic/cp.php 'widgets' => [ [ // [tl! focus:start] 'type' => 'local_weather', 'width' => 100, ], // [tl! focus:end] ], ``` ================================================ FILE: content/collections/pages/building-an-addon.md ================================================ --- id: 5bd75435-806e-458b-872e-7528f24df7e6 blueprint: page title: 'Building an Addon' template: page updated_by: 42bb2659-2277-44da-a5ea-2f1eed146402 updated_at: 1569264134 intro: 'An addon is a composer package you intend to reuse, distribute, or sell. For simple or private packages, consider implementing directly into your Laravel application.' --- ## Creating an Addon You can generate an addon with a console command: ``` shell php please make:addon example/my-addon ``` This will scaffold out everything you need to get started as a [private addon](#private-addons) within your site's `addons` directory. Eventually, an addon may be available on Packagist and installable through Composer (and therefore live inside your `vendor` directory). During development however, you can keep it on your local filesystem as a path repository. :::tip If you don't plan on distributing your addon or sharing it between multiple projects, you can take a simpler approach and just [add things to your Laravel application](/extending). ::: ### What's in an addon? An addon consists of at least a `composer.json` and a service provider. Your directory may be placed anywhere, but for the sake of this example, we'll put it in `addons/acme/example` ``` files theme:serendipity-light addons/ acme/ example/ src/ ServiceProvider.php composer.json app/ content/ config/ public/ index.php resources composer.json ``` ### Composer.json The composer.json is used by (you guessed it) Composer in order to install your package. The `extra.statamic` section is used by Statamic to know that it's an addon and not just a standard Composer package. The `extra.laravel.providers` section what Laravel uses to load your service provider. ``` json { "name": "acme/example", "description": "Example Addon", "autoload": { "psr-4": { "Acme\\Example\\": "src" } }, "authors": [ { "name": "Jason Varga" } ], "support": { "email": "support@statamic.com" }, "extra": { "statamic": { "name": "Example", "description": "Example addon" }, "laravel": { "providers": [ "Acme\\Example\\ServiceProvider" ] } } } ``` ### Service Provider The service provider is where all the various components of your addon get wired together. You should make sure that your service provider extends Statamic's `Statamic\Providers\AddonServiceProvider`, and not `Illuminate\Support\ServiceProvider`. Statamic's `AddonServiceProvider` includes some bootstrapping and autoloading that isn't included with Laravel's service provider. ``` php call('some:command'); }); } ``` ## Registering Components Statamic will autoload _most_ of your addon's components, as long as they're in the right place and named correctly. However, you can still register them manually in your service provider if you need to: ``` php protected $tags = [ \Acme\Example\Tags\First::class, \Acme\Example\Tags\Second::class, // etc... ]; protected $modifiers = [ // ]; protected $fieldtypes = [ // ]; protected $widgets = [ // ]; protected $commands = [ // ]; ``` ## Assets ### CSS and Javascript We recommend using Vite to build CSS and JavaScript for your addon. For full setup instructions, please see our [Vite Tooling](/addons/vite-tooling) docs. ### Publishables You may also mark generic assets for publishing by providing a `publishables` array with the full path to the origin and the destination directory. ``` php protected $publishables = [ __DIR__.'/../resources/images' => 'images', ]; ``` ### Publishing assets When using the `$vite`, `$scripts`, `$stylesheets`, and `$publishables` properties, these files will be made available to the `artisan vendor:publish` command. They will all be tagged using your addon's slug. Whenever the `statamic:install` command is run (i.e. after running `composer update`, etc) the following command will be run: ``` shell php artisan vendor:publish --tag=your-addon-slug --force ``` You can prevent these from being automatically published by adding a property to your provider: ``` php protected $publishAfterInstall = false; ``` This may be useful if you need more control around groups of assets to be published, or if you're using custom [post-install commands](#post-install-commands). ## Routing ### Registering Routes Addons can register three types of routes: * Control Panel routes * Action routes * Web routes To keep things organized, we recommend keeping your routes in separate files. ``` files theme:serendipity-light / src/ routes/ cp.php actions.php web.php ``` If you follow this convention, Statamic will automatically register these route files for you. If you prefer to keep them elsewhere, you can register them manually in your service provider: ``` php protected $routes = [ 'cp' => __DIR__.'/../routes/cp.php', 'actions' => __DIR__.'/../routes/actions.php', 'web' => __DIR__.'/../routes/web.php', ]; ``` #### Control Panel Routes Control Panel routes will be automatically prefixed by `/cp` (or whatever URL the control panel has been configured to use) and will have authorization applied. We recommend prefixing routes with your addon's name but we didn't enforce this explicitly to give you a bit more flexibility. #### Action Routes Action routes will be prefixed by `/!/addon-name` and are generally intended as front-end "actions" your addon may expose without being a prominent section of the website. For example, somewhere to process a form submission. #### Web Routes Web routes have no prefix and no Statamic middleware attached. They will be added at the root level, as if you were adding them to a standard Laravel app's `routes/web.php` file, giving you complete control. However, as a Laravel route, they will have the `web` middleware attached. ### Writing Routes When referencing a controller in a route, it will automatically be namespaced to your addon's root namespace. ``` json "autoload": { "psr-4": { "Acme\\Example\\": "src" } }, ``` ``` php Route::get('/', [ExampleController::class, 'index']); // Acme\Example\ExampleController ``` If you'd prefer not to have separate route files, you can write routes in your service provider's `bootAddon` method. ``` php public function bootAddon() { $this->registerCpRoutes(function () { Route::get(...); }); $this->registerWebRoutes(function () { Route::get(...); }); $this->registerActionRoutes(function () { Route::get(...); }); } ``` Other than that, you're free to write routes [as per any Laravel application](https://laravel.com/docs/routing). ### Route Model Binding Statamic uses [route model binding](https://laravel.com/docs/routing#route-model-binding) to automatically convert some route parameters into usable objects. Words aligning with core Statamic concepts will automatically be converted to their appropriate objects: `collection`, `entry`, `taxonomy`, `term`, `asset_container`, `asset` ,`global`, `site`, `revision`, `form`, and `user` You're free to use these words as your route parameters, but be aware they will automatically attempt to convert to the respective objects. For example: ``` php public function example(Request $request, $entry) { // Given a route of "/example/{entry}", when visiting "/example/123" // $entry will be an Entry object with an ID of 123. // There will be a 404 if an entry with an ID of 123 doesn't exist. } ``` ## Middleware You may push your own middleware onto respective middleware groups using the `$middlewareGroups` property. The keys are the names of the groups, and the values are arrays of middleware classes to be applied. ``` php protected $middlewareGroups = [ 'statamic.cp.authenticated' => [ YourCpMiddleware::class, AnotherCpMiddleware::class ], 'web' => [ YourWebMiddleware::class ], ]; ``` Available middleware groups are: | Group | Description | |-------|-------------| | `web` | Front-end web requests, defined in the project's `App\Http\Kernel` class. | `statamic.web` | Statamic-specific front-end web requests. This includes routes that correspond to content (like entries), as well as manually defined routes using `Route::statamic()`. These will also have `web` middleware applied. | `statamic.cp` | All control panel requests (even ones not protected by authentication, like the login page). | `statamic.cp.authenticated` | Control panel routes behind authentication. Anything in there can assume there will be an authenticated user available. These will also have the `statamic.cp` middleware applied. ## Views Any views located in your `resources/views` directory will automatically be available to use in your code using your package name as the namespace. ``` files theme:serendipity-light / src/ resources/ views/ foo.blade.php ``` ``` php // assuming your package is named vendor/my-addon return view('my-addon::foo'); ``` If you want to customize the namespace, you can set the `$viewNamespace` property on your provider: ``` php protected $viewNamespace = 'custom'; ``` ``` php return view('custom::foo'); ``` ## Inertia The Control Panel is powered by [Inertia.js](https://inertiajs.com), which lets Statamic render pages as Vue components while still using Laravel’s server-side routing. Using Inertia for your custom pages is strongly recommended if you want them to match the SPA-like behaviour seen throughout the Control Panel. To expose a Vue page component to Statamic, register it in your `cp.js` file: ```js import Foo from './pages/Foo.vue'; Statamic.booting(() => { Statamic.$inertia.register('my-addon::Foo', Foo); }); ``` Then return that page from your controller: ```php use Inertia\Inertia; return Inertia::render('my-addon::Foo', [ 'message' => 'Hello world!', ]); ``` All data passed to `Inertia::render()` becomes props on the Vue component. For proper SPA behaviour, make sure your page uses Inertia’s `` component to set the document title, and use `` instead of `` so navigation stays instant and avoids a full refresh: ```vue ``` ## Events Statamic will automatically register any event listeners in the `src/Listeners` directory, as long as the event is type-hinted in the listener's `handle` or `__invoke` method. ``` php use Acme\Example\Events\OrderShipped; class SendShipmentNotification { public function handle(OrderShipped $event) { // } } ``` Subscribers will also be autoloaded, as long as they live in `src/Subscribers`. If your addon's listeners or subscribers live elsewhere, you may register them manually in your service provider: ``` php protected $listen = [ \Acme\Example\Events\OrderShipped::class => [ \Acme\Example\Listeners\SendShipmentNotification::class, ], ]; protected $subscribe = [ \Acme\Example\Listeners\UserEventSubscriber::class, ]; ``` To learn more about defining events, listeners and subscribers, please consult the [Laravel event documentation](https://laravel.com/docs/events). ## Scheduling To define a schedule from your addon, you can add a `schedule` method and schedule tasks just like you typically would in a Laravel application's `routes/console.php` file. ``` php protected function schedule($schedule) { $schedule->command('something')->daily(); } ``` Consult the [Laravel scheduling documentation](https://laravel.com/docs/scheduling#defining-schedules) to learn how to define your schedule. ## Editions An addon can have various editions which enable you to limit your features depending on which is selected. For example, you could have a free edition with limited features, and an edition with extra features that requires a license. ### Defining Editions You can define your editions in your `composer.json`. They should match the edition handles that you set up on the Marketplace. ``` json { "extra": { "statamic": { "editions": ["free", "pro"] } } } ``` :::best-practice The first edition is the default when a user hasn't explicitly selected one. Your editions should be listed from least to most expensive because that's the nice thing to do. ::: ### Feature Toggles You can check for the configured edition in order to toggle features. ``` php $addon = Addon::get('vendor/package'); if ($addon->edition() === 'pro') { // } ``` :::tip You don't need to check whether a license is valid, Statamic does that automatically for you. ::: ## Settings Laravel config files are great for storing application settings, but they're not ideal for settings you might want users to edit through the Control Panel. You can register a settings blueprint in your addon to give users a friendly interface for managing settings. Drop a blueprint file in `resources/blueprints/settings.yaml` or register it in your service provider like this: ```php public function bootAddon() { $this->registerSettingsBlueprint([ 'tabs' => [ 'main' => [ 'sections' => [ [ 'display' => __('API'), 'fields' => [ [ 'handle' => 'api_key', 'field' => ['type' => 'text', 'display' => 'API Key', 'validate' => 'required'], ], // ... ], ], ], ], ], ]); } ``` Your addon's settings page will show up in the Control Panel under **Tools -> Addons**. Pretty convenient. You can even reference config options (and by extension environment variables) in your settings blueprint using Antlers, like so: `{{ config:app:url }}`. Settings are stored as YAML files in `resources/addons` by default, but can be moved to the database if you prefer. Just run the `php please install:eloquent-driver` command and you're all set. You can retrieve the settings using the `Addon` facade: ```php use Statamic\Facades\Addon; $addon = Addon::get('vendor/package'); // Getting settings... $addon->settings()->get('api_key'); $addon->settings()->all(); $addon->settings()->raw(); // Doesn't evaluate Antlers // Setting values... $addon->settings()->set('api_key', '{{ config:services:example:api_key }}'); $addon->settings()->set([ 'website_name' => 'My Awesome Site', 'api_key' => '{{ config:services:example:api_key }}', ]); // Saving... $addon->settings()->save(); ``` ## Update Scripts You may register update scripts to help your users migrate data, etc. when new features are added or breaking changes are introduced. For example, maybe you've added a new permission and want to automatically give all of your existing form admins that new permission. To do this, create a class which extends the `UpdateScript` class and implement the necessary methods: ``` php use Statamic\UpdateScripts\UpdateScript; class UpdatePermissions extends UpdateScript { public function shouldUpdate($newVersion, $oldVersion) { return $this->isUpdatingTo('1.2.0'); } public function update() { Role::all()->each(function ($role) { if ($role->hasPermission('configure forms')) { $role->addPermission('configure goat-survey-pro')->save(); } }); $this->console()->info('Permissions added successfully!'); } } ``` The `shouldUpdate()` method helps Statamic determine when to run the update script. Feel free to use the `isUpdatingTo()` helper method, or the provided `$newVersion` and `$oldVersion` variables to help you write this logic. The `update()` method is where your custom data migration logic happens. Feel free to use the `console()` helper to output to the user's console as well. In the above example, we assign the new `configure goat-survey-pro` permission to all users who have the `configure forms` permission. That's it! Statamic should now automatically run your update script as your users update their addons. ## Testing Statamic automatically scaffolds a PHPUnit test suite when you generate an addon with `php please make:addon`. To learn more about writing addon tests, please review our [Testing in Addons](/extending/testing-in-addons) guide. ## Publishing to the Marketplace Once your addon is ready to be shared, you can publish it on the [Statamic Marketplace](https://statamic.com/marketplace) where it can be discovered by others. Before you can publish your addon, you'll need a couple of things: - Publish your Composer package on [packagist.org](https://packagist.org). - Create a [statamic.com seller account](https://statamic.com/creator/begin) - If you're planning to charge for your addons, you'll need to link connect your bank details to your seller account. In your seller dashboard, you can create a product. There you'll be able to link your Composer package that you created on Packagist, choose a price, write a description, and so on. Products will be marked as drafts that you can preview and tweak until you're ready to go. Once published, you'll be able to see your addon on the Marketplace and within the Addons area of the Statamic Control Panel. ## Addons vs. Starter Kits Both addons and starter kits can be used to extend the Statamic experience, but they have different strengths and use cases: ### Addons - Addons are installed via `composer`, like any PHP package - Addons live within your app's `vendor` folder after they are installed - Addons can be updated over time - Addon licenses are tied to your site :::tip An example use case is a custom fieldtype maintained by a third party vendor. Even though the addon is installed into your app, you still rely on the vendor to maintain and update the addon over time. ::: ### Starters Kits - Starter kits are installed via `statamic new` or `php please starter-kit:install` - Starter kits install pre-configured files and settings into your site - Starter kits do not live as updatable packages within your apps - Starter kit licenses are not tied to a specific site, and expire after a successful install :::tip An example use case is a frontend theme with sample content. This is the kind of thing you would install into your app once and modify to fit your own style. You would essentially own and maintain the installed files yourself. ::: ================================================ FILE: content/collections/pages/caching.md ================================================ --- title: Caching intro: Caching is the life-blood, the secret-sauce, and the wizard behind the curtain of Statamic. There are several caching layers, each with its own purpose. Let's explore each one and its specific purpose. id: bde2385f-5fee-4cb1-a516-5fe2e2d17e0c blueprint: page --- ## The Stache **Purpose:** _Replace the traditional database_ Instead of using a relational database like MySQL as a storage system, Statamic aggregates the data in your content files into an efficient, index-based system and stores it in Laravel's application cache. We call this the "stache", and we like to make mustache jokes about it.
    Tom Selleck as Magnum P.I.
    Behold, the stache of all staches!
    **The stache is ephemeral** and can be blown away and rebuilt from scratch at any time without losing data. This is most often done when content or settings change, or when updates are deployed to a production server. The [CLI](/cli) has commands to clear, warm, and refresh (clear and then immediately warm) the stache. ``` shell php please stache:clear php please stache:warm php please stache:refresh ``` There are settings you can configure to improve the performance of the stache, just like with a relational database. [Learn more about the Stache](/stache) and its various settings. :::tip **You cannot disable the stache** — it is critical architecture. ::: ## Application cache **Purpose:** _Make site faster_ The application cache is used by your site/application, third-party addons, Laravel Packages, and Statamic itself to store queries, data, and the results of resource intensive operations for pre-defined lengths of time. It uses [Laravel’s Cache API](https://laravel.com/docs/cache). **For example,** the [Image Transform](/tags/glide) feature uses this cache to store all the manipulated images at their various sizes and transformations. When the arguments that generate those manipulations change, the cached images are blown away and new ones are generated and cached. Each item inserted into the cache can **optionally and automatically** expire after a specified length of time. If you want to clear the entire application cache at once, use the `artisan cache:clear` command. ``` shell php artisan cache:clear ``` :::tip The Stache is stored **inside** the application cache, so if you clear it, you don't need to _also_ clear the Stache. ::: ## View fragments **Purpose:** _Make a view faster_ There are times when you may want to simply cache a section of an Antlers template to reduce load times for a particularly "expensive" bit of logic or content fetching. This is where the [cache tag](/tags/cache) comes in. Wrap your markup in `{{ cache }}` tags, specify a duration, and your site is zippy and, one might say, quite delicious once again. ::tabs ::tab antlers ```antlers {{ cache for="1 hour" }} something impressive but slow here {{ /cache }} ``` ::tab blade ```blade something impressive but slow here ``` :: ## Static caching **Purpose:** _Ultimate speed at the cost of dynamic features_ There is nothing faster on the web than static pages, except static pages without JavaScript and giant hero images, of course. Statamic can cache static pages and pass routing off to Apache or Nginx through reverse proxying. Static Caching can be enabled on a per-page level, allowing you to mix and match dynamic features when needed. :::tip This should not be confused with [Static Site Generation](https://github.com/statamic/ssg), which is the fastest possible way to run your site, involving generating actual `.html` files used to serve your site, skipping PHP and the Statamic application entirely. ::: [Learn more about Static Caching](/static-caching) to make your sites start flying! We're talking `2ms` response times here. ================================================ FILE: content/collections/pages/cli.md ================================================ --- title: CLI intro: Statamic provides developers a nice long list of scripts available in the command line. They can clear caches, create users, generate addon and extension classes, and perform other time-saving tasks. In short, they make a developer's job easier and more enjoyable. template: page id: 83145e6c-45d2-4e9c-a412-48a81f144224 blueprint: page --- ## Overview Statamic's CLI commands are built with [Laravel's Artisan Console package][artisan]. To view the list of Statamic-specific commands, you may use the `please list` command: ``` shell php please list ``` To see _all_ commands available, including those provided by Laravel, use the `artisan list` command. ``` shell php artisan list ```` ## Artisan vs Please There is no functional difference between Artisan and Please. `please` is merely an alias for `php artisan statamic:`. We just think manners are still important and it feels nice to treat your command line with respect, while saving you a little time typing. ``` shell # These are equivalent php please make:user php artisan statamic:make:user ``` Every command also includes a help screen which displays and describes the command's available arguments and options. To view a help screen, precede the name of the command with help: ``` shell php please help make:user ``` ## Available commands {#commands} You can see the list of available commands in your terminal by running `php please list`. But for those who don't feel like it, haven't installed yet, or are scared of the command line, here they are also. | Command | Description | |---------|-------------| | `install` | Install Statamic | | `list` | List all the Statamic commands | | `multisite` | Converts from a single to multisite installation | | `addons:discover` | Rebuild the cached addon package manifest | | `assets:clear-cache` | Clear the `asset_meta` and `asset_container_contents` cache stores | | `assets:generate-presets` | Generate asset preset manipulations | | `assets:meta` | Generate asset metadata files | | `assets:meta-clean` | Clean orphaned asset metadata files | | `auth:migration` | Generate Auth Migrations | | `eloquent:import-groups` | Imports file based groups into the database. | | `eloquent:import-roles` | Imports file based roles into the database. | | `eloquent:import-users` | Imports file based users into the database. | | `flat:camp` | Flat Camp ⛺ | | `glide:clear` | Clear the Glide image cache | | `install:collaboration` | Installs the Statamic Collaboration addon and enables broadcasting in Laravel. | | `install:eloquent-driver` | Install & configure Statamic's Eloquent Driver package | | `install:ssg` | Install & configure Statamic's Static Site Generator package | | `license:set` | Set Statamic license key in .env | | `make:action` | Create a new action | | `make:addon` | Create a new addon | | `make:dictionary` | Create a new dictionary | | `make:fieldtype` | Create a new fieldtype | | `make:filter` | Create a new filter | | `make:modifier` | Create a new modifier | | `make:scope` | Create a new query scope | | `make:tag` | Create a new tag | | `make:user` | Create a new user account | | `make:user-migration` | Makes the user migration file | | `make:widget` | Create a new widget | | `migrate-dates-to-utc` | Migrates dates in your content from your current timezone to UTC. | | `nocache:migration` | Generate Nocache Migrations | | `pro:enable` | Enable Statamic Pro in .env | | `search:insert` | Insert an item into its search indexes | | `search:update` | Update a search index | | `site:clear` | Start a fresh site, wiping away all content | | `stache:clear` | Clear the "Stache" cache | | `stache:doctor` | Diagnose any problems with the Stache. | | `stache:refresh` | Clear and rebuild the "Stache" cache | | `stache:warm` | Build the "Stache" cache | | `starter-kit:export` | Export a starter kit package | | `starter-kit:init` | Creates a new starter kit config | | `starter-kit:install` | Install a starter kit | | `static:clear` | Clear the static page cache | | `static:warm` | Warm the static cache by crawling all URLs | | `support:details` | List useful details to help with support | | `support:zip-blueprint` | Create a zip file with a blueprint and all fieldset imports | | `updates:run` | Run update scripts from a specific version | ## Additional reading Read more about [Artisan][artisan] at Laravel.com. [artisan]: https://laravel.com/docs/artisan ================================================ FILE: content/collections/pages/code-of-conduct.md ================================================ --- id: 0efc0286-bfb1-4d54-8a94-8589b35adf88 blueprint: page title: 'Code of Conduct' updated_by: 3a60f79d-8381-4def-a970-5df62f0f5d56 updated_at: 1632425727 --- This is the Statamic Code of Conduct. By participating here, you are expected to uphold this code like a knight of old. This code of conduct applies to all spaces used by the Statamic community for communication. This includes the Statamic Discord, forums, GitHub, Twitter, Facebook, meetups, conferences, and any other relevant forums. If you believe someone is violating the code of conduct, we ask that you report it by contacting us at [statamic.com/support](https://statamic.com/support). Your identity will remain confidential. - **Treat others in the way you want to be treated.** Respect each other, we’re all on the same team here so let’s have fun, share what we know, and hopefully learn something new! No bullying, harassment, foul language, racism, sexism, or other negative "isms". Generally, just be awesome to each other. - **There is always another side to every story.** When interpreting the words and actions of others, always assume good intentions. Sometimes it's difficult, but everyone has a bad day now and then. Someday it might be your turn. - **Don’t be afraid to ask.** There is no such thing as a dumb question. We’re all here to learn and we encourage people to ask questions about anything Statamic-related. - **Don't bash other platforms** (e.g. WordPress, Craft, ExpressionEngine, etc). They all serve their purposes, markets, and provide livelihoods for thousands of developers. Please be welcoming to those coming from those communities to check out Statamic. Hopefully they'll find something they love, but it won't happen every time. :blush: ================================================ FILE: content/collections/pages/collections.md ================================================ --- id: 7202c698-942a-4dc0-b006-b982784efb03 blueprint: page title: Collections intro: 'Collections are containers that hold groups of related entries. Each entry in a collection can represent a blog post, product, recipe, or even chapter of your Family Matters fan fiction novel detailing Steve Urkel''s rise to UFC Heavyweight Champion of the world.' template: page related_entries: - 7202c698-942a-4dc0-b006-b982784efb03 - 8d9cfb16-36bf-45d0-babb-e501a35ddae6 - 6177b316-0eed-4dec-83d1-e5a48a8e00b6 - dcf80ee6-209e-45aa-af42-46bbe01996e2 - a6a956fd-647d-4503-9a4a-3b24198e6e73 - 54548616-fd6d-44a3-a379-bdf71c492c63 - cb21fabb-65ba-4869-9acd-f6aa2fb58a01 - 420f083d-99be-4d54-9f81-3c09cb1f97b7 --- ## Overview Not to be redundant, but Collections are simply containers that hold entries. You can think of them like shoeboxes containing love letters, except they're folders on your server and they're holding text documents. So, not exactly the same thing — or at least, not nearly as romantic. Each collection holds settings that affect all of its entries. Like URL patterns by way of [routes](/routing), which fields are available with [blueprints](/blueprints), as well as any desired [date behaviors](#dates). You can also set default values for system fields like template, blueprint, and published status. A collection is defined by a YAML file stored in the `content/collections` directory. All accompanying entries will be stored in a sub-directory with a matching name. For example, a `blog` collection looks like this: ``` files theme:serendipity-light content/collections/ blog/ hello.md is-it-me.md youre-looking-for.md blog.yaml ``` :::tip Creating a collection in the control panel takes care of all of this for you automatically, so don't stress too hard about memorizing all the details. ::: ## Entries Each entry has, at the very least, a title, published status, id, and _usually_ additional content fields. These content fields are determined by one or more [blueprints](/blueprints) set on the collection. Entries are stored as Markdown files inside their collection's respective directory (`content/collections/{collection}/entry.md`). At any time you can edit any entry in your code editor by popping open these files and doing what comes naturally. ### An example Let's to pretend it's the summer of '99 and we are journalists covering the Summer X Games. The weather here in San Fransisco is beautiful and 275,000 people are watching Tony Hawk make history. Here's an entry we might write about the event.
    An entry being edited in the Statamic control panel An entry being edited in the Statamic control panel
    Entry publishing with only the default content fields.
    And here's what the Markdown file would look like: ``` markdown --- title: Tony Hawk lands the first-ever 900 id: 3a28f050-f8d2-4a56-ba8a-314a9d46bf38 --- It took skateboarding legend Tony Hawk 11 tries, but he finally landed a 900 at the 1999 Summer X Games in a moment that launched the sport into popular consciousness in a new way. ``` You can create, edit, and delete entries in the control panel _or_ filesystem, it's up to you and your preference in the heat of the moment. Let your passion carry you away. ### View data Each entry has its own unique URL. When you're on that URL in your web browser, all of the entry's data will be available in your views as variables. If an entry is _missing_ data, intentionally or not, it will fall back to a series of defaults. We call this fallback logic [the cascade](/cascade). If a value doesn't exist in one place, it'll check the next place, then the next, and so on, in this order: 1. The entry 2. The origin entry if using localization (the entry it was localized from) 3. The collection ### Setting default data {#inject} **Injecting** data into your collection allows you to provide default values for your entries. If entries have these variables set, they will override the collection _defaults_, but not any data set on the entries themselves. This is done by adding an `inject` key in your collection's YAML config file. ``` yaml # /content/collections/blog.yaml [tl! **] title: Blog date: true date_behavior: past: public future: private route: 'blog/{slug}' sort_dir: desc template: blog/show inject: #[tl! focus:start] author: jason show_sidebar: true show_newsletter_signup: false #[tl! focus:end] ``` ## Blueprints Each collection uses blueprints to define the available fields when creating and editing its entries. When you create a new collection, a blueprint of the same name will be createed for you as your default. It contains a very basic set of fields: title, `content`, `slug`, `author`, and `date`, if the collection is configured to store dates. You can customize this blueprint as you wish, as well as create your own additional blueprints. If you create _more than_ one blueprint you'll be given the option to choose which one you want when creating a new entry. While this isn't common, it can be a pretty powerful option in the right situations. You can hide blueprints from appearing in the new entry menu by activating the _Hidden_ toggle on the blueprint's UI or setting `hide: true` in the blueprint's yaml file. ## Titles All entries require a title. Statamic uses titles to display entries in a consistent way throughout the Control Panel. Depending on the purpose of the collection, a dedicated `title` field might not be useful to you. In this case, you may configure a "title format" which would be used to automatically generate titles from other fields so you don't have to invent something every time. For example, a "reviews" collection might just have `author`, `stars`, and `content` fields. You could configure the titles to be "5 star rating by John Smith".
    Entry Title Format Setting Entry Title Format Setting
    Configuring an automated title
    When using multiple sites, you may optionally configure the titles on a per site level by using an array: ```yaml title_format: en: '{stars} star rating by {author:name}' fr: '{stars} étoiles par {author:name}' ``` It's worth noting that changes to a collection's title format won't change the titles of existing entries. For it to take effect, you will need to re-save your existing entries. :::tip To use modifiers in title formats, make sure to use the `{{` Antlers syntax, like this: ```antlers {{ headline | ucfirst }} ``` ::: ## Slugs Slugs are used in entry URLs. For an entry named `My Entry`, the slug would default to `my-entry` unless you edit it. Slugs are automatically generated for you based on the title, but if you edit them, that automatic process is switched off. We trust you know what you're doing. ### Disabling Slugs If the entries in a specific collection don't need to have dedicated URLs, or if the entries' route only contains other fields, a `slug` field may not be useful for you. You may disable the slug requirement by adding a boolean: ```yaml slugs: false ``` This will prevent collections from automatically adding a slug field. :::tip Since Statamic stores entries as files, it uses the slug for the filename. If you disable slugs, it will use the ID instead. (e.g. `123.md` instead of `my-entry.md`) ::: ## Dates If your collection entries require a date — as they often do — you can decide how Statamic uses it to control visibility. For example, you can choose to have dates set in the future to be private (404), which allows you to schedule their publish date. Alternatively, you could have _past_ dates be private which would make entries act like "upcoming events" that disappear from a list when they're over.
    Collection Date Behaviors Collection Date Behaviors
    Just imagine! This could be you, configuring date behaviors.
    ### Available date behaviors Each of these behaviors is available for future and past dates. - **public** - Entries will be visible in listings and at their own URLs. - **unlisted** - Entries will be hidden in listings but available at their own URLs. - **private** - Entries will be hidden in listings, and their own URLs will 404. :::tip Date behaviors are _defaults_. They can be overridden at the [tag level](/tags/collection) in your templates. ::: ### Date behavior and published status You can override [date behavior visibility settings](#available-date-behaviors) by setting the **Publish by Default** option to `false`. Each entry will automatically be assigned one of four possible computed `status` values, which respects both your collection's date behavior settings, as well as your entry's published setting: - **published** - Entry is published and visible. - **scheduled** - Entry is published, but not yet visible because date is upcoming. - **expired** - Entry is published, but not visible anymore because date has expired. - **draft** - Entry is explicitly hidden via `published: false`. :::tip We recommend [filtering](/tags/collection#published-status) and [querying](/repositories/entry-repository#get-all-published-and-scheduled-entries) against your entry's `status` (instead of its `published` boolean) so that you can more easily take advantage of date behavior logic without hassle. :::
    Collection Published Status Filtering Collection Published Status Filtering
    Filter by entry status in your collection listings.
    ## Time **Time** may be enabled on your [date field](/fieldtypes/date) to have entries publish at a **specific time**, e.g. `11:45am`, or to ensure that multiple entries in the same day are published in chronological order. We recommend leaving the **Time Enabled** setting on. You can also enable the **Show Seconds** setting if you need to publish more than one entry per minute. :::tip If you don't enable the time, _all_ entries on a given day will assume a default time of midnight, or `00:00`. ::: ## Scheduling If you've added a date and/or time to your entries in order to "schedule" them, you may need to set up the scheduler in order for Statamic to properly invalidate your cache to display them at the right time. For example, you might need things to happen exactly when an entry is scheduled, like refreshing a cached blog listing, or sending a notification. [Learn how to use the scheduler](/scheduling). ## Ordering Flick on the "Orderable" switch in a collection's settings and you'll have a drag and drop UI in the control panel to order the entries. The collection is now "structured". Learn more about [structures](/structures).
    An orderable collection An orderable collection
    You can tell these entries are orderable because of the way they are.
    :::tip Order will take precedence when sorting. For example, if you make a dated collection **orderable**, date will no longer be the default sort order. You still can sort by date by specifying `sort="date"` on your [collection tag](/tags/collection). ::: ### Constraining Depth A structured collection will **not** have a maximum depth unless you set one, allowing you to nest entries as deep as you like. Set the `max_depth` option to limit this behavior. Setting the **Max Depth** option to `1` will replace the tree UI with a flat, table-based UI.
    An orderable collection with max depth of 1 An orderable collection with max depth of 1
    These reorderable entries have a max depth of 1.
    ### Default sort order in listings For non-structured collections, you can choose which field and direction to sort the list of entries in the Control Panel by setting the `sort_by` and `sort_dir` variables in your collection.yaml. By default, the Title field will be used. ### Root page If you specify that your collection should "expect a root page", the first item in the tree UI will be considered the "root". This entry will _not_ use a slug in its URI — it will be treated as a `/`. The most common usage for this is to define a home page in a pages' collection. In this example, the root page's url would be `/` instead of `/home`. But this would also be true of a sub-section. If you had an ordered `documents` collection that was set up to live at `/documents/`, the "root" of that collection in this case would be the `/documents/` URL. ## Routing Entries receive their URLs from their collection's route setting. You can use standard meta variables in addition to the variables from the collection's blueprint to define your route rule. You can even use [computed values](/computed-values) or Antlers to create more complicated dynamic route logic. ``` yaml route: /blog/{slug} ``` If you are building a multi-site and want different routes for different locales: ```yaml route: english: /events/{slug} french: /evenements/{slug} ``` :::tip Statamic does not automatically define route rules. If you want entries in your new collection to have URLs (almost always the case), make sure you define one! ::: ### Meta variables | Variable | Available | |----------|-----------| | `slug` | always | | `year` | when in a dated collection | | `month` | when in a dated collection | | `day` | when in a dated collection | | `parent_uri` | when in an [orderable](#ordering) collection and max_depth > 1 | | `depth` | when in an [orderable](#ordering) collection and max_depth > 1 | | `mount` | when [mounted](#mounting) to an entry | ### Example Routes Here are a few examples of possible route rules for inspiration. 💡 #### Wordpress style ``` yaml route: /news/{year}/{month}/{day}/{slug} # example: /news/2019/01/01/happy-new-year ``` #### For when you don't care about SEO ``` yaml route: /{id} # example: /12345-1234-321-12345 ``` #### For when you care _too much_ about SEO ``` yaml route: /{parent_uri}/{slug}.html # example: /details/project.html ``` #### Organizing sports brackets with structures Here's how we use the `depth` variable, along with the `team_name` field from the entry's blueprint. ``` yaml route: /tournament/round-{depth}/{team_name} # example: /tournament/round-4/chicago-bulls ``` #### Using fields from related entries For example, if you have a `category` field in your Products collection and you'd like for your product URLs to depend on it, you can configure a [computed value](/computed-values) to return the category URL, then use that computed value in your collection's route: ``` php // app/Providers/AppServiceProvider.php use Statamic\Facades\Collection; Collection::computed('products', 'category_url', function ($entry, $value) { return $entry->category?->url(); }); ``` ``` yaml route: '{{ category_url }}/{{ slug }}' # example: /categories/wooden-toys/steam-locomotive ``` #### Using Antlers to organize gaming articles You can even use Antlers to get more complicated. Here we'll include the [mounted](#mounting) entry at the top level. ``` yaml mount: 'id-of-games-page' route: '{{ depth == 1 ?= mount }}/{{ parent_uri }}/{{ slug }}' # example: /games/zork/how-to-play/controls ``` :::tip If you're using Antlers in your route, you must use `{{ double curlies }}` when referencing variables. ::: ### Index route Once you've set up a route for your entries (e.g. `/blog/{slug}`) you'll usually want an index page listing all your entries as well. It's important to know that Statamic **doesn't** create this for you automatically. You need to either: - Create an entry in another collection (typically a "pages" collection) that exists as `/blog` and [mount](#mounting) it to your blog collection. - Create a [custom route](/routing#statamic-routes) that exists at `/blog`. ## Redirects Adding a `redirect` variable to one of your entries will cause an HTTP redirect when visiting its URL. ``` yaml --- id: page-book-tickets title: Book Ticket redirect: http://booking.mysite.com ``` A particularly useful example of when you might want to do this is if you need an external link in your nav but creating a completely separate nav would be overkill. The following redirects are supported: - external links (starting with `http`) - internal links (starting with `/`) - other entries or terms (eg. `entry::id-of-entry` or `term::id-of-term`) - its first child page (`@child`) - If there are no child pages you will get a 404 - a `404` response Any other strings will be assumed to be a relative link. For example: if the page URL is `/my/page` and you have `redirect: is/here` in your entry, you will be redirected to `/my/page/is/here`. By default, Statamic will return a 302 status code when redirecting. To return a different status code, make the `redirect` an array with a `status` key: ```yaml redirect: url: http://booking.mysite.com status: 301 ``` :::tip Entries with redirects will get filtered out of the [collection](/tags/collection) tag by default. You can include them by adding a `redirects="true"` parameter. ::: ### Entry link blueprint When a Collection is structured and you have set `Entries in this collection may contain links (redirects) to other entries or URLs.` on in the collection settings, you will be presented with the option to create "Links" along with any other available blueprints when you try to create an entry. This will load a behind-the-scenes blueprint containing `title` and `redirect` fields. You are free to modify what's shown on these pages by creating your own `entry_link` blueprint. ## Taxonomies [Taxonomies](/taxonomies) are defined on the _collection level_, not the blueprint-level. This enforces a tighter content-model, and reduces complexity when configuring blueprints. Let's imagine you have a **product** collection. Each entry is a product, and each product _has one or more_ categories. Thus set, no matter what blueprints you configure, each will have a **categories** field in the sidebar. You'll be able to access any categories on your entries with a `{{ categories }}` variable loop. ### Taxonomies setting ``` yaml taxonomies: - categories - tags ``` ## Mounting You may "mount" a collection onto an entry in your collection config as a way of saying "all these entries belong to this section". When you do this, two neat things happen: - The collection's entries will become subpages of the entry. E.g. `/blog/that-one-time-at-dev-camp` - If the entry is in a structured collection with a nav tree, you will see shortcut links to **add or edit** entries in that collection, like the Blog page in the screenshot below.
    Mounted collections in a structure Mounted collections in a structure
    Look at those add and edit links!
    ## Search indexes You can configure search indexes for your collections to improve the efficiency and relevancy of your users' searches. Learn [how to connect indexes](/frontend/search#connecting-indexes). ## Revisions Revisions allow you to see the history of any given entry over time. Revisions need to be enabled on the site level ([read those docs](/revisions)), and then you can enable them for any collection in your collection config. ## Labels Throughout the control panel you may find buttons that say "Create Entry". If you would rather them say something more specific (for example, "Create Article"), you may customize them per-collection by adding a translation key. In `lang/en/messages.php`, you can add `{handle}_collection_create_entry` with the appropriate label. ```php 'Create Article', ]; ``` You may add the same key to `messages.php` in other language directories as necessary. ## Localization When running a [multi-site](/multi-site) installation, you can have entries exist in multiple sites with different content, or have entries exclusive to a site. [Read about localizing entries](/tips/localizing-entries) ================================================ FILE: content/collections/pages/command-palette.md ================================================ --- id: 3482755d-3d20-42d5-8680-301a1cb95965 title: Command Palette --- The command palette provides handy access to many pages and actions in the Control Panel without having to leave your keyboard.
    Command Palette Command Palette
    Make friends with the `⌘K` shortcut 😎
    Out of the box, it provides things like: - Content search - Control panel navigation - Recently visited pages - Intelligent page-specific and contextually relevant actions - Links to relevant documentation - Access to user preferences, light/dark mode, etc. ## Extending the Command Palette If you're [extending the CP nav](/extending/cp-navigation), the command palette will automagically populate itself with those nav items 🎉 However, you may find yourself in a situation where you need to add more custom items to the command palette. You can do this in a variety of ways... ### PHP If you wish to add basic links from PHP, simply add the following to a service provider, or to a controller for page-specific links: ```php use Statamic\Facades\CommandPalette; CommandPalette::add( text: 'Staff Calendar' url: '/custom-laravel-route', icon: 'calendar', ); ``` #### Advanced Link Example You can also pass an array to `text` if you want to use the same arrow conventions Statamic uses throughout the command palette (ie. `Search » Ancient » Hotbot`). Or maybe you want to configure whether to `openNewTab`, or even disable `trackRecent` to prevent it from showing up in recent items, etc. ```php use Statamic\Facades\CommandPalette; CommandPalette::add( text: ['Search', 'Ancient', 'Hotbot'], url: 'https://hotbot.com', icon: 'sexy-robot', openNewTab: true, trackRecent: false, ); ``` ### JavaScript JavaScript can also be a great place to add page-specific links, or even contextually relevant actions that might require JS logic. Parameter-wise, the JS API mostly mirrors the parameter set of the PHP API, with a few key differences and additions: 1. The `add()` method is available via the global `Statamic.$commandPalette` helper: ```js Statamic.$commandPalette.add({ text: ['Search', 'Ancient', 'Hotbot'], url: 'https://hotbot.com', icon: 'sexy-robot', openNewTab: true, }); ``` 2. The JS API allows you to specify custom `action` and `when` functions, for controlling `when` the item is visible in realtime, or for running custom JS `action` logic on selection: ```js Statamic.$commandPalette.add({ text: 'Celebrate', icon: 'star', when: () => entryIsPublished(), action: () => throwConfetti(), }); ``` 3. The JS API gives you a bit more control over where the item is displayed in the command palette. For example, though items are always fuzzy-searchable, they are normally rendered further down in the `Miscellaneous` section of the command palette. You can increase visibility by moving them to top section of the command palette by putting them into the `Actions` category: ```js Statamic.$commandPalette.add({ // ... category: Statamic.$commandPalette.category.Actions, }); ``` On busier pages, you can also `prioritize` primary callout style actions to the very very top, since they normally default to alphabetical order within the section: ```js Statamic.$commandPalette.add({ // ... prioritize: true, }); ``` 4. The `trackRecent` option for `url` based link items defaults to `false` on the JS side, because things tend to be more context-specific on the JS side. Of course, you can override this: ```js Statamic.$commandPalette.add({ // ... trackRecent: true, }); ``` ### Template Component Sometimes you'll find yourself in a situation where you want to use the JS API to wire up a simple link or button in your template to your command palette, and you don't want to have to extract out to a JS component to do so. For these situations, you may use the `` component, which is a Vue component that wraps the [JS API](#javascript): ```html
    Hotbot ``` If you want to dry up duplication, you may also use the `v-slot` to reuse things like `text`, `icon`, `url`, etc. ```html ``` ================================================ FILE: content/collections/pages/computed-values.md ================================================ --- title: 'Computed Values' blueprint: page intro: 'Define dynamic values on your data and display them as virtual fields in the Control Panel. They''re like accessors on Eloquent models.' id: 0327afd5-469b-4119-a75e-2bfe9389eb05 --- ## Overview Think of computed values as virtual fields that can be composed from any source. You could be grabbing a value from a secondary local database, a 3rd party API, or even by composing a dynamic value from other fields on the entry itself. ## Setting computed values Inside a service provider's `boot` method, you can configure dynamic computed field data on [Collections](/collections) and [Users](/users) using the provided `computed()` helper on the relevant Facade. ### On user instances For example, maybe you wish to return a user's `balance` using a 3rd party invoicing API: ```php use Statamic\Facades\User; User::computed('balance', function ($user, $value) { return InvoicingService::balance($user->email()); }); ``` ### On entry instances Or maybe you wish to return a `shares` count on entries within your `articles` collection using a 3rd party social media API: ```php use Statamic\Facades\Collection; Collection::computed('articles', 'shares', function ($entry, $value) { return TooterService::shareCount($entry->permalink); }); ``` If you want to use the same computed value across multiple collections, you may provide an array of collections instead: ```php use Statamic\Facades\Collection; Collection::computed(['articles', 'pages'], 'shares', function ($entry, $value) { return TooterService::shareCount($entry->permalink); }); ``` You can also provide multiple computed values for the same collection using an associative array: ```php Collection::computed('articles', [ 'shares' => function ($entry, $value) { return TooterService::shareCount($entry->permalink); }, 'likes' => function ($entry, $value) { return TooterService::likeCount($entry->permalink); }, ]); ``` ### Overriding using stored values The second `$value` parameter in the `computed()` callback function will return a _stored_ value under the same handle, if one exists, allowing you to override computed values if necessary. For example, maybe you wish to display an article's `subtitle` if one is saved on the entry, otherwise fall back to a truncated version of the entry's `description` value: ```php use Statamic\Facades\Collection; use Statamic\Support\Str; Collection::computed('articles', 'subtitle', function ($entry, $value) { return $value ?? Str::limit($entry->value('description'), 25); }); ``` ### Performance If you plan on accessing data through a 3rd party API, or even computing values across large data sets locally, it may be beneficial to cache your data. :::tip You can use Laravel's [Cache](https://laravel.com/docs/cache#cache-usage) facade to store and retrieve cached values within your computed callback function. ::: ## Getting computed values Once configured, you can simply access your computed values as properties on your instances (ie. `$user->balance` or `$entry->shares`). :::tip Computed values are only available for **top-level** fields. You can't use them inside Replicator or Grid fields. Likewise, computed values can't be queried as they're only evaluated after the query has been executed. ::: ### Showing computed values in the control panel Or view your computed values in the control panel if you configure your blueprint to allow for it. The first step is to add a field with your computed value's `handle`:
    Computed field handle Computed field handle
    Next, set your field `Visibility` to `Computed`. This will ensure your field is displayed on your Publish Form as a read-only field [that will not store any data on save](/fields#field-data-flow):
    Computed field visibility config Computed field visibility config
    You may also show this field as a column on your listings using the `Listable` setting, as shown above:
    Computed field visibility config Computed field visibility config
    One of us didn't win anything, but does he need the money anyway?
    ## Computed default values Sometimes you want a field's default value to be dynamic — pulled from a config file, an addon setting, the current user, or any other runtime source. You can register a **computed default** closure and reference it from any field's `default` config. Register the callback inside a service provider's `boot` method using the `Field` facade: ```php use Statamic\Facades\Field; Field::computedDefault('default_timezone', function () { return config('app.timezone'); }); ``` Then reference it from your blueprint or fieldset using the `computed:` prefix followed by the key you registered: ```yaml fields: - handle: timezone field: type: text default: 'computed:default_timezone' # [tl!**] ``` The closure will be resolved each time a new entry is created, so the default stays fresh. Stored values on existing entries are untouched. :::tip Computed defaults are great for addon authors — register a default that reads from your addon's settings so users see a sensible initial value without hardcoding it into every blueprint. ::: ================================================ FILE: content/collections/pages/conditional-fields.md ================================================ --- title: 'Conditional Fields' intro: Show and hide fields in your publish forms based on conditions and triggers. For example, you may only want to show a caption field if an asset field has an image selected, or a whole block of fields if a toggle switch is enabled. template: page id: dd52c1f6-661b-4408-83c6-691fa341aaa7 blueprint: page related_entries: - aa96fcf1-510c-404b-9b63-cea8942e1bf8 - 54548616-fd6d-44a3-a379-bdf71c492c63 --- ## Overview Field conditions are set on individual field settings in [blueprints](/blueprints). For example, you could create a `meta_description` field that is only shown and submitted when the `content` field is longer than 140 characters.
    Statamic conditional field rule builder Statamic conditional field rule builder
    The conditional field rule builder
    You may specify various rules for showing a field under either the `if` / `show_when` keys, or hiding a field under the `unless` / `hide_when` keys. ### Data flow Only visible fields are submitted with your form data. This allows you to control data flow, and [conditionally apply validation](#validation) to visible fields when needed. If you require conditionally hidden fields to be saved with your data, you may use the `always_save` config option (read more about [field data flow](/fields#field-data-flow)). :::tip If you want to cosmetically hide a larger set of fields to get them out of the user's way, you can use the [Revealer](/fieldtypes/revealer) fieldtype to hide them until the user needs them without disrupting data flow on form submission. ::: ## Boolean A simple example might be to show a field when a toggle is set to 'on': ```yaml - handle: has_author field: type: toggle - handle: author field: type: text if: has_author: true ``` ## Empty If you need to show a field based on whether another field is empty or not, you can use `empty` or `not empty`: ```yaml - handle: favorite_food field: type: text - handle: second_favorite_food field: type: text if: favorite_food: not empty ``` ## Equality Maybe you might wish to show various fields based on the value of a select field: ```yaml - handle: post_type field: type: select options: - text - video - handle: content field: type: text if: post_type: text - handle: youtube_id field: type: text if: post_type: video ``` ## Contains If you are dealing with an array of options, you can conditionally show fields when an array contains specific value(s) using `contains` or `contains_any`: ```yaml - handle: favorite_foods field: type: checkboxes options: - pizza - lasagna - oatmeal - handle: favorite_topping field: type: text if: favorite_foods: 'contains pizza' - handle: favorite_italian_singer field: type: text if: favorite_foods: 'contains_any pizza, lasagna' ``` If you are dealing with a string value, `contains` and `contains_any` will perform sub-string checks instead: ```yaml - handle: favorite_food field: type: text - handle: favorite_topping field: type: text if: favorite_food: 'contains pizza' - handle: favorite_italian_singer field: type: text if: favorite_food: 'contains_any pizza, lasagna' ``` ### Taxonomy Terms When you want to compare against a taxonomy term, the `contains` term needs to include the taxonomy handle, like `taxonomy::slug`: ```yaml - handle: favorite_food field: type: text - handle: food_groups field: type: terms taxonomies: - food_groups display: Food Groups mode: select - handle: favorite_vegetables field: type: text if: favorite_food: 'contains food_group::vegetables' ``` ## Advanced comparisons For more advanced comparisons, several operators and right-hand-side literals/options are available to you. For example, we could show an `email` field if age is greater than or equal to `16`: ```yaml - handle: age field: type: text - handle: email field: type: text if: age: '>= 16' ``` Available operators include: | Operator | Description | | :--- | :--- | | `is` `equals` `==` | Loose equality comparison (inferred if no operator is used). | | `not` `isnt` `!=` | Loose inequality comparison. | | `===` | Strict equality comparison. | | `!==` | Strict inequality comparison. | | `>` | Greater than comparison. | | `>=` | Greater than or equal to comparison. | | `<` | Less than comparison. | | `<=` | Less than or equal to comparison. | | `contains` `includes` | Check if array contains a value, or if a string contains a sub-string value. | | `contains_any` `includes_any` | Check if array contains any of a comma-separated list of values, or if a string contains any of a comma-separated list of sub-strings values. | Available right-hand-side literals/options include: | Literal / Option | Description | | :--- | :--- | | `empty` | Will intelligently check if value is empty (ie. `null`, `''`, `[]`, or `{}`). | | `null` | Will be evaluated as a **literal** `null`. | | `true` | Will be evaluated as a **literal** `true`. | | `false` | Will be evaluated as a **literal** `false`. | ## Multiple conditions If you define multiple field conditions, all conditions need to pass for the field to be shown (or hidden if you use the `unless` / `hide_when` parent key). For example, the following will show the field when `this_field` is `bacon` *__AND__* `that_field` is `cheeseburger`: ```yaml if: this_field: bacon that_field: cheeseburger ``` If you want to show a field when _any_ of the conditions pass, you can append `_any` onto the parent key. For example, the following will show the field when `this_field` is `bacon` *__OR__* `that_field` is `cheeseburger`: ```yaml if_any: this_field: bacon that_field: cheeseburger ``` ## Nested fields You may use dot notation to access nested values when necessary. For example, maybe you would like to show a field when an `array` fieldtype's `country` value is `Canada`: ```yaml if: address.country: Canada ``` ## Field context By default, conditions are performed against values in the current level of `fields` in your blueprint. If you need access to values outside of this context (eg. if you are in a replicator, trying to compare against fields outside of the replicator), you can access parent field values by prepending your field with `$parent`: ```yaml if: $parent.favorite_foods: includes bacon ``` You can also access values at the top-level of your blueprint with `$root`: ```yaml if: $root.favorite_foods: includes bacon ``` ## Extra values In addition to field values, a handful of "extra" values are made available so you can write conditions against native data that isn't part of the blueprint. ### Assets When editing an asset, the following values are available: | Value | Description | | :--- | :--- | | `filename` | The filename including extension (e.g. `beach.jpg`). | | `basename` | The filename without the extension (e.g. `beach`). | | `extension` | The file extension (e.g. `jpg`). | | `path` | The full path to the asset within its container. | | `mimeType` | The mime type (e.g. `image/jpeg`). | | `width` | The width in pixels, for assets with dimensions. | | `height` | The height in pixels, for assets with dimensions. | | `duration` | The duration in seconds, for audio and video. | For example, to only show an `Autoplay` toggle on video assets shorter than one minute: ```yaml - handle: autoplay field: type: toggle display: Autoplay if: extension: mp4 duration: '<= 60' ``` ### Entries When editing an entry in a structured collection, the following value is available: | Value | Description | | :--- | :--- | | `depth` | The depth of the entry in the structure. Top-level entries are `1`. | For example, to only show a field on entries nested deeper than two levels: ```yaml if: depth: '> 2' ``` ## Custom logic If you need something more complex than the YAML syntax provides, you may write your own logic. In a [JS script](/extending/control-panel) or addon, you can define custom functions using the `$conditions` JS API: ```yaml if: quote: custom isCanadian ``` ```javascript Statamic.$conditions.add('isCanadian', ({ target }) => { return new RegExp('eh|bud|hoser').test(target); }); ``` :::warning It's worth noting that custom conditions only work in the Control Panel, not in the context of frontend forms. ::: ### Parameters You may also pass parameters to your custom functions: ```yaml if: hero_video_url: 'custom isFiletype:mp4' hero_image_url: 'custom isFiletype:jpg,png' ``` ```javascript Statamic.$conditions.add('isFiletype', ({ target, params }) => { return new RegExp(params.join('|') + '$').test(target); }); ``` ### Without Target If you need to perform a condition against multiple hardcoded values, you can bypass setting a target field in the yaml by referencing your function name at the top of your `if` condition: ```yaml if: reallyLovesFood ``` ```javascript Statamic.$conditions.add('reallyLovesFood', ({ values }) => { return (values.meals.length + values.desserts.length) > 10; }); ``` ### Field context Furthermore, if you need access to values outside of the current [field context](#field-context), we also provide a `root` values parameter, as well as access to the Publish Container object via `container`: ```javascript Statamic.$conditions.add('...', ({ root, container }) => { // }); ``` ## Validation If you wish to conditionally apply validation to conditionally shown fields, we recommend using the `sometimes` [Laravel validation rule](https://laravel.com/docs/validation#validating-when-present). ```yaml - handle: online_event field: type: toggle - handle: venue field: type: text if: online_event: false validate: - sometimes - required ``` The above example will only _sometimes_ apply the `required` rule to the `venue` field; Only when it exists in the submitted form data (see notes on [data flow](#data-flow) above). :::tip For more advanced conditional validation, take a look at Laravel's `required_if`, `required_with`, etc. [validation rules](https://laravel.com/docs/validation#rule-required-if). ::: ## Templating You can take advantage of Conditional Fields on your front-end Forms to automatically generate dynamic forms and logic. [Learn more about it](/tags/form-create#conditional-fields). ================================================ FILE: content/collections/pages/conditions.md ================================================ --- id: 9751908a-a10c-4c36-abd3-2251e83fbc65 blueprint: page title: 'Tag Conditions' template: page intro: 'Conditions allow you to filter the results of your content tags (e.g. Collections, Taxonomies) using the data inside them, much like WHERE clauses do with SQL.' updated_by: 3a60f79d-8381-4def-a970-5df62f0f5d56 updated_at: 1632512130 --- :::tip Are you looking for "if/else" conditions? You probably want this page: [Antler's Logic & Conditions](/antlers#conditions) ::: ## Overview Quite often you'll find that you don't want to fetch _all_ entries from a collection, or _all_ terms from a taxonomy. Conditions give the ability to fetch only the content that meets the criteria of your choosing. For example, you may want to list all entries that _aren't_ the one you're viewing, are after your birthday 🎂 but before Christmas 🎄, or have a custom field like `pinned` 📌 set to `true`. Piece of cake. Piece of crumb cake. 🥮 _Note: These conditions currently apply to the [collections](/tags/collection), [taxonomy](/tags/taxonomy), and [users](/tags/users) tags._ ## Syntax The conditions syntax has 3 parts: the field name, the condition name, and the value.
    {field_name}:{condition}="{value}"
    ### Field name The field name is the name of the field you're filtering on. ### Using a variable reference If you prefix the field name with a colon, it will use the value of a variable in your view ``` :author:is="author" ``` ### Multiple values You can pass multiple values by separating them with a pipe. ``` taxonomy:category="happy|radical" ``` ### Comparisons For conditions where you're matching or comparing a value `is` or `starts_with`, you'd do: ::tabs ::tab antlers ```antlers {{ collection:blog title:starts_with="Once upon a time..." }} ``` ::tab blade ```blade ``` :: ### Boolean For boolean conditions like `exists` or `null`, specify a value of `true`: ::tabs ::tab antlers ```antlers {{ collection:blog hero_image:exists="true" }} ``` ::tab blade ```blade ``` :: For _negative_ boolean conditions, _don't_ use `="false"`. Instead, pick the inverse condition, like `:exists` instead of `:doesnt_exist`. ::tabs ::tab antlers ```antlers // Nope {{ collection:articles related_articles:exists="false" }} // Yup {{ collection:articles related_articles:doesnt_exist="true" }} ``` ::tab blade ```blade // Nope // Yup ``` :: ### Multiple Conditions Need multiple conditions? Yeah, we support that. ::tabs ::tab antlers ```antlers {{ collection:drinks type:is="tiki" ingredients:in="Orgeat" }} {{ title }} {{ /collection:drinks }} ``` ::tab blade ```blade {{ $title }} ``` :: ### Passing multiple values To pass multiple _values_ in a condition, separate them with `|` pipes. ::tabs ::tab antlers ```antlers {{ collection:drinks ingredients:in="rum|falernum" }} {{ title }} {{ /collection:drinks }} ``` ::tab blade ```blade {{ $title }} ``` :: ### Sub fields You can apply conditions to "sub fields", like date ranges: ```yaml event_date: start: 2023-12-01 end: 2023-12-03 ``` ::tabs ::tab antlers ```antlers {{ collection:events :event_date.start="today" }} {{ collection:events :event_date.end="today" }} ``` ::tab blade ```blade ``` :: ## String conditions The following conditions apply to fields with data stored as strings. | Condition | Description | | :--- | :--- | | `is` / `equals` | Include if field **is equal** to value. | | `not` / `isnt` | Include if field is **not equal** to value. | | `exists` / `isset` | Include if field **exists**. | | `doesnt_exist` / `is_empty` / `null` | Include if field **doesn't exist**. | | `contains` | Include if field **contains** value. | | `doesnt_contain` | Include if field **doesn't contain** value. | | `in` | Include if field value is **in** the provided array. | | `not_in` | Include if field value is **not in** the provided array. | | `starts_with` | Include if field **starts with** value. | | `doesnt_start_with` | Include if field **doesn't start** with value. | | `ends_with` | Include if field **ends with** value. | | `doesnt_end_with` | Include if field **doesn't end with** value. | | `gt` | Include if field is **greater than** value. | | `gte` | Include if field is **greater than or equal to** value. | | `lt` | Include if field is **less than** value. | | `lte` | Include if field is **less than or equal to** value. | | `matches` / `regex` | Include if field **matches** case insensitive regex. | | `doesnt_match` | Include if field **doesn't match** case insensitive regex. | | `is_alpha` | Include if field contains **only alphabetical characters**. | | `is_numeric` | Include if field contains **only numeric characters**. | | `is_alpha_numeric` | Include if field contains **only alphanumeric characters**. | | `is_url` | Include if field **is a valid URL**. | | `is_embeddable` | Include if field **is an embeddable video URL**. | | `is_email` | Include if field **is valid email address**. | | `is_after` | Include if field **is after** date. | | `is_before` | Include if field **is before** date. | | `is_numberwang` | Include if field **is numberwang**. | ## Array conditions The following conditions apply to fields with data stored as an array. | Condition | Description | | :--- | :--- | | `overlaps` | Include if any field value **matches** the provided array (has). | | `doesnt_overlap` | Include if **no** value **matches** the provided array (has not). | ## Taxonomy conditions [Taxonomy](/taxonomies) conditions are a little bit different. They start with `taxonomy:`, followed by the taxonomy name, an optional modifier argument, and finally the term you're seeking.
    taxonomy:{handle}:{modifier}="{term}"
    ### Query modifiers {#taxonomy-query-modifiers} You may optionally control the behavior of the condition filter by passing the desired modifier into the tag method call. If you don't set a modifier, it will use `any` by default. #### Any (default) {#taxonomy-any} Fetch all entries that have _any_ of one or more taxonomy terms. #### Not {#taxonomy-not} Fetch all entries that _don't_ have one or more taxonomy terms. #### All {#taxonomy-all} Fetch all entries that contain _each_ of one or more taxonomy terms. ### Examples {#taxonomy-examples} ::tabs ::tab antlers ```antlers {{ collection:articles taxonomy:tags:any="featured" }} {{ collection:articles taxonomy:tags="featured" }} (shorthand) {{ collection:articles taxonomy:tags:not="sports" }} {{ collection:articles taxonomy:tags:all="gaming|featured" }} ``` ::tab blade ```blade (shorthand) ``` :: ## Arguments | Argument | Description | | :--- | :--- | | `{handle}` | Handle of the Taxonomy you wish to query. | | `{modifier}` | Control the behavior of the condition filtering. Available options: `all`, `not`, and `any`. Default: `any`. | | `{term}` | Term(s) to query. You may pass multiple terms by separating them with `\|` pipe delimiters. | ## Snippets Here are some common and useful conditions snippets to grab on your next project. ### Exclude the current entry ``` :id:not="id" ``` ### Entries with specific "tags" Assuming you have a taxonomy named "Tags"... ``` taxonomy:tags="review|colorful" ``` ### Show draft (unpublished) entries ``` status:is="draft" ``` ### Published before a specific date Let's use Y2K as the example date. ``` date:is_before="2000-01-01" ``` ================================================ FILE: content/collections/pages/configuration.md ================================================ --- title: Configuration intro: Statamic uses standard Laravel config files and environment variables for application-level settings. template: page blueprint: page id: 10d236ff-a80b-4d88-afa8-fe882b0f37a2 --- ## Config files Statamic's config files are located in `config/statamic/`. They are PHP files named by area of responsibility. ``` files theme:serendipity-light config/statamic/ antlers.php api.php assets.php autosave.php cp.php editions.php forms.php git.php graphql.php live_preview.php markdown.php oauth.php protect.php revisions.php routes.php search.php stache.php static_caching.php system.php templates.php users.php webauthn.php ``` ## Environment variables It is often helpful to have different configuration settings based on the environment where the site is running. For example, you may wish to enable debug mode on your local server but not your production server :::warning **Never enable Debug Mode or DebugBar on production.** The error messages — as beautiful as they are — will reveal much about the way your site is configured, where important files are, and possibly even leak data from your `.env` file depending on how you use those variables. ::: In a fresh Statamic installation you'll find an `.env.example` file in the root directory of your site. Rename or copy it to `.env` to enable it. If you install Statamic via Composer or the [CLI tool](https://github.com/statamic/cli), this will be done automatically for you. ### Environment variable types Variables in your `.env` files are parsed as strings. In order to handle a wider range of types, some specific values are reserved. | `.env`   Value | Parsed Value | |--------------|--------------| | `true` | `(bool) true` | | `(true)` | `(bool) true` | | `false` | `(bool) false` | | `(false)` | `(bool) false` | | `empty` | `(string) ''` | | `(empty)` | `(string) ''` | | `null` | `(null) null` | | `(null)` | `(null) null` | If you need to define an environment variable with a value containing a space, you may do so by enclosing the value in double quotes. ``` env APP_NAME="Gluten Free Potato Canons" ``` ### Retrieving environment variables All environment variables are available in your config files by using the `env()` helper function. An optional second argument allows you to pass a default value. ``` php // config/app.php 'awesome' => env('ENABLE_AWESOME', true), ``` ::tabs ::tab antlers Once passed into a config file, the variable can be used in your views with the `{{ config }}` tag. ``` antlers // To retrieve the above 'awesome' value... {{ config:app:awesome }} ``` ::tab blade Once passed into a config file, the variable can be used in your views with the `config()` helper function. ```blade // To retrieve the above 'awesome' value... {{ config('app.awesome') }} ``` :: :::warning **Your `.env` file should never be committed to version control**. Each developer or server running your application may require a different configuration, not to mention it can be a security risk in the event your version control repository is ever made public. Any sensitive credentials — like API keys and secret tokens — would be visible. ::: ### Hiding environment variables from debug pages When an exception is uncaught and the `APP_DEBUG` environment variable is `true`, the debug page will show all environment variables and their contents. You may obscure variables by updating the `debug_blacklist` option in your `config/app.php` config file. ``` php return [ // ... 'debug_blacklist' => [ '_ENV' => [ 'APP_KEY', 'MAILCHIMP_API_KEY', 'BITCOIN_WALLET_PW', ], '_SERVER' => [ 'APP_KEY', 'DB_PASSWORD', ], '_POST' => [ 'password', ], ], ]; ``` Learn more about [environment configuration](https://laravel.com/docs/configuration#environment-configuration) in the Laravel docs. ================================================ FILE: content/collections/pages/content-managers-guide.md ================================================ --- id: e947dc19-9a8a-44c2-911c-171d1f196c91 blueprint: page title: 'Content Manager’s Guide to Statamic' nav_title: 'Content Manager’s Guide' breadcrumb_title: "Content Manager's Guide" intro: |- So you've got yourself a brand new Statamic site, inherited an older one, or joined a team that's already using Statamic. Great! Welcome to the wonderful world of Statamic. Perhaps you're wondering what to do next, how to add a tracking code to a landing page, get technical support, or reset your password and get back into the Control Panel. You've come to the right place. We'll try to answer the most common end-user questions and topics people have when encountering Statamic for the first time. --- ## Four things you need to know. ### We can’t get into your site. This is important information, so please read this whole section. All Statamic sites are **self-hosted**. This means that each Statamic site is a unique _copy_ of the Statamic application, and is running on a server that you, your company, or web design/development agency owns or has access to. And since your Statamic site is running on a server that isn't ours, it means we **do not have access to it**. We can't sign in to your control panel and reset a password for you, give a user more permissions or access, make changes to the site, read the code, or kick out annoying users with bad grammar. It might seem like that's a big downside if you're running into a problem that you feel our support team should be able to fix from our floating cloud desks — and maybe in a few cases that might be true — but overall this is a very good thing for you and your organization. Nothing we can do can take your site away. **Your site is yours forever**. Too many people have put their hard work and money building websites, blogs, and companies futures on platforms that end up getting shut down, bought out, or change their mind about who their audience should be — resulting in prices skyrocketing, or sites just disappearing forever with no way to get data out. Things change so fast in the tech world — your site **should not** be subject to these external (and often desperate) forces. Own your site. Own your content. Own your audience. ::: Did you know? Servers are just computers that run websites and applications accessible to the internet. And **"the cloud"** is just another term for "a bunch of servers out there somewhere". They might as well be floating around in the sky as far as most people are concerned or care. ::: ### Every site is unique. Each Statamic site starts like a bit of a blank slate. We take a "start simple and add things as needed" approach to features and settings, in contrast to other platforms that take a "everything is included and rip out what you don't want" approach. This means that Statamic doesn't do everything automatically, and generally requires a developer to enable, configure, or hook up different features you might assume are part of every site. A few examples of this in practice: blog posts don't automatically have "tags" or "categories" — in fact there is no "blog" by default, and there is no built-in "snippet manager" to paste analytics or tracking codes into. Each of these things can be "built" in a matter of minutes (or even seconds) with Statamic's building-block approach to custom fields and features. This might feel like it's backwards if you're used to working in a platform like WordPress or Drupal, but we have found (and our customers agree emphatically!) that it's much better in the long run to _turn on_ the things you need, enable features you plan to use, and name things _the way you want for clarity_, than to spend precious time clicking about the control panel disabling all the things you'll never need, or worse — just leaving behind features, buttons, or sections that simply don't do anything. You may find that your site is missing the ability to edit something you consider to be fundamental in a content management system. It might frustrate you. You might feel stuck. But before reaching for WordPress to rebuild your whole site from scratch (please don't do that), just have a quick chat with whoever built your site, because... ### Statamic sites are _very_ easy to change. **This is Statamic's secret power.** This is why developers use Statamic over the ubiquitous WordPress. **Do not be afraid** to ask the developer or team who built your Statamic site to make a change, make some bit of content editable, or add a new feature. Most often, it will be a very small amount of work.

    God, do I love working with Statamic. It feels like implementing anything only takes a fraction of the time compared to other content management systems...

    — Martin Keck, Developer

    ### Every Statamic site needs a developer. Because of these three facts, it's important to note that every Statamic site needs — at least at one point or another — a web developer. Someone who can write some code and get it up on a server. Statamic is pretty easy to learn for anyone who knows HTML and has run other PHP applications (like WordPress, for example) before. But if this isn't your skill set, it's okay. Just know that most people who build websites have the skills necessary to work on a Statamic site (assuming they're willing to read some of this documentation). Once your site is set up and launched, it's possible you may not need another developer again, or at least for a long time. But, in order to run updates to make sure you're running the fastest and most secure version of Statamic, you'll need that developer every now and then to do that. Statamic updates aren't a "click the button and wait" kind of process. There are too many potential problems with that approach to web software, and in order to protect you and your website from going down, all Statamic updates must be run from the command line — a level of access a regular user doesn't have. ## FAQs ### Where do I login to my Statamic site's Control Panel? Most sites have their login screen set up on `example.com/cp` (where example.com is the URL of your website). This URL is customizable, and some people like to change it to `example.com/admin` or something else entirely for security purposes. Someone with access to your site's Statamic Control Panel will need to create your account and invite you first. As part of that process you will _most likely_ have received an email with a link to activate your account. This link will get you to your Control Panel. We say _most likely_ because these defaults can be changed. Instead of an email, they could have sent you a link into Slack or Teams, etc. :::tip Your [statamic.com](https://statamic.com) account (if you have one) has nothing to do with the login for your site's control panel. It's just used to buy licenses, addons, and request support. This is because [we can't get into your site](#we-cant-get-into-your-site). ::: ### How do I reset my password? No worries, it happens to the best of us! Assuming you know your login URL (see [Where do I login to my Statamic site's Control Panel?](#where-do-i-login-to-my-statamic-sites-control-panel)), you should have a link to **Reset Your Password** right there on that screen.
    Statamic Password Reset Link
    👆 See that Forgot password link?
    After clicking that link and entering your email address, you should receive an email with a link to reset your password and get back into the Control Panel. If you don't receive that email, it's possible that whoever built your site (your developer) didn't set up email sending for your site and/or server. In this circumstance, you have three options. 1. Ask your developer to finish setting up your site properly and/or [reset your password manually](/tips/manually-resetting-a-user-password) for you. **This is the best option** because it prevents this problem from happening again. 2. Ask someone else with permissions to manage users in Control Panel to reset your password for you. They can do that by going to the Users section, then opening the dropdown next to your account and clicking "Copy Password Reset Link" and sending that to you in your preferred communication tool of choice. 3. Ask someone else with permissions to manage users in Control Panel to simply change your password for you. They can do that by going to the Users section, then clicking on your user account, and then the "Change Password" button next to your name, and following the form that pops up. ### What do I do if my site is broken? There are a few reasons a site might "randomly" break, but in almost all of those cases you'll need to contact your developer to fix your site and bring it back online. They _should_ know to check the error logs to see what happened, but feel free to suggest that to them. Also mention the exact error code, if there is one, that you see when visiting your site. A 404 error is very different from a 501, for example. We'll explain a few of the most common (though this kind of thing is anything but common) scenarios when this might happen. #### DNS settings are wrong It's possible that there's nothing wrong with your site, but rather with your DNS settings. DNS settings are usually managed with your Domain Registrar — the place you bought your .com, .co.uk, or other domain name. These settings tell browsers where to go when visiting your domain. This is most likely the problem if your site is showing the _wrong_ site, wrong page, or displays an error that mentions DNS, redirects, or something similar. If someone has recently been making changes to your DNS (maybe setting up a subdomain or changing email providers), it's possible they changed the wrong thing and your DNS is no longer pointing at your server properly. If this happens, contact your developer or whoever manages your DNS to have them undo whatever mess they made. #### Surprise server upgrade It's possible your server had an upgrade that significantly changed the version of PHP or some other bit of software that your version of Statamic doesn't support. This is similar to when you upgrade a Mac or Windows computer — sometimes you need to upgrade your software to compatible with the latest versions of your operating system. This is rare, but can happen with cheap "shared hosting" services, like GoDaddy, HostGator, BlueHost, and other similar companies that have crazy low prices. You get what you pay for with hosting. If this happens, you'll need to have a developer update Statamic to the latest version, and if that doesn't fix it, open a support ticket with your host. And if that doesn't fix it, it might be time to get a better host. #### Server ran out of storage If your website has lots and lots of images (or really big images) and files, it's possible your server may have run out of storage space while Statamic was creating all thumbnails and any other image sizes needed to render your design. If this happens, you'll need to have your developer clear out any image caches and upgrade the storage capacity of your server so it doesn't happen again. Alternately, your developer can move your images to AWS (Amazon's file management service) or an equivalent, as it can be more cost effective than upgrading your server. #### Bad code Nobody's perfect. It's possible your developer had some custom code in your site that has a performance problem and uses up all your memory, thereby crashing the server — or something like that. If this happens, you guessed it — talk to your developer. They'll figure it out. And if they don't, [get a better developer](https://statamic.com/partners). ### How do I find a new Statamic developer? If a developer or agency is no longer available to work on your site, or you feel their quality of work is not up to your standards, it may be time to find a new Statamic developer. If you start Googling "where to hire a Statamic developer" you _may_ not find a ton out there, at least when compared to WordPress developers, but let us assure you, there are lots of talented and professional Statamic folks out there. The best place to find one is on our [Partners](https://statamic.com/partners) directory. There you can find a list of freelancers and agencies who have a proven track record of building Statamic sites and addons. Furthermore, the **Certified Partners** have gone through reference vetting and code reviews with our Core Team to make sure they're technically proficient and have good relationships with their clients. We highly recommend working with a Certified Partner. If you'd like us to match you with a Partner, we'd be happy to do so. Head on over to our [Matchmaking Service](https://statamic.com/partners/matchmaking). You could also post a job or project on [workwithstatamic.com](https://workwithstatamic.com), or jump into our live [Discord Chat](https://statamic.com/discord) and visit the `#jobs` channel to see who might be interested in helping you out. ================================================ FILE: content/collections/pages/content-modeling.md ================================================ --- id: 884507b0-77ac-469e-a99f-f646065d5954 title: 'Content Modeling' blueprint: link redirect: url: '@child' status: 301 --- ================================================ FILE: content/collections/pages/content-queries.md ================================================ --- id: e7833062-e05c-42c9-ad35-dc5077f1f0b8 blueprint: page title: 'Content Queries' intro: 'Statamic provides a fluent query builder interacting with your content and data in PHP-land. If you think of them as Laravel Eloquent Models, you should feel right at home.' --- ## Overview Each of the core Statamic data types has its own Facade used to access an underlying repository class so you can query, create, modify, and delete content. Working with data in this manner is usually done in a [Controller](/controllers), with any retrieved data being passed into a view. These methods will work no matter which driver you're using — flat files, Eloquent/MySQL, or any other custom repo driver. [Learn how Statamic can use different storage methods!](/extending/repositories) :::tip While Statamic's Query builder is very similar to [Laravel's Query Builder](https://laravel.com/docs/13.x/queries), they are **completely separate implementations**. What follows is complete documentation on all available methods. If you need a method available in Laravel that we don't currently support, feel free to open a [feature request](https://github.com/statamic/ideas) or better yet, a [Pull Request](https://github.com/statamic/cms)! ::: ## Retrieving data There are two different types of classes you'll interact with while querying content: Repositories and Query Builders. ### Repositories Each Facade interacts with a **repository**, which allows you to get data about the desired data type. For example, you can use the `Entry` Facade to get an entry, or the `GlobalSet` Facade to get all the variables inside of it. ```php use Statamic\Facades\Entry; use Statamic\Facades\GlobalSet; Entry::find('abc123'); GlobalSet::findByHandle('footer')->inDefaultSite()->get('copyright'); ``` ### Query builders Some Facades also have a **Query Builders** that allows you to query, filter, and narrow down the results you desire. The `Entry` Facade's Query Builder allows you to find all the entries in a collection, by a specific author, and so on. :::tip All Query Builders are part of a Repository, but not all Repositories have a Query Builder. Just like how all donuts are desserts, but not all desserts are donuts. 🍩 ::: **Query Builders** allow you to assemble a query, chain additional constraints onto it, and then invoke the `get` method to get the results: ```php use Statamic\Facades\Entry; $entries = Entry::query() ->where('collection', 'blog') ->limit(5) ->get(); ``` This would return a `Collection` of the items. In this particular example, you would have a `Collection` of `Entry` objects. ### Examples {.popout} #### Getting a single record If you only want to get a single record, you may use the `first` method. This method will return a single data object: ```php Entry::query() ->where('collection', 'blog') ->first(); ``` #### Getting specific fields This method is really only helpful when using a database — it improves query speed by performing column `SELECT`s behind the scenes. ```php Entry::query() ->where('collection', 'blog') ->get(['title', 'hero_image', 'content']); ``` ## Basic where clauses ### Where You may use the query builder's `where` method to add "where" clauses to the query. The most basic call to the `where` method requires three arguments. The first argument is the name of the field. The second argument is an operator, which can be any of the supported operators. The third argument is the value to compare against the field's value. For example, the following query gets entries where the value of a `status` field is `featured`. ```php Entry::query() ->where('status', '=', 'featured') // [tl! ~~] ->get(); ``` As a shorthand for an "equals" query, you may pass the value as the second argument to the `where` method. Statamic will assume you would like to use the `=` operator: ```php Entry::query()->where('status', 'featured')->get(); ``` You can chain where clauses, filtering records based on more than one condition with AND: ```php Entry::query() ->where('status', '=', 'featured') ->where('status', '!=', 'sticky') ->get(); ``` This same query can also be written using one where clause: ```php Entry::query() ->where([ ['status', '=', 'featured'], ['status', '!=', 'sticky'] ]) ->get(); ``` You can query entries across multiple conditions using `orWhere()`: ```php Entry::query() ->where('status', '=', 'featured') ->orWhere('status', '=', 'sticky') // [tl! ~~] ->get(); ``` ### WhereBetween The `whereBetween` method lets you verify that a field's value lies between two values that you pass: ```php Entry::query() ->whereBetween('numeric_field', [0, 1000]) // [tl! ~~] ->get(); ``` You can also use the `whereNotBetween` method to verify that a field's value does not lie between two values that you pass: ```php Entry::query() ->whereNotBetween('numeric_field', [0, 1000]) // [tl! ~~] ->get(); ``` Note: `orWhereBetween` and `orWhereNotBetween` are also supported. ### WhereColumn The `whereColumn` method lets you compare a field's value to that of another field: ```php Entry::query() ->whereColumn('published', '=', 'status') // [tl! ~~] ->get(); ``` Note: `orWhereColumn` is also supported. ### WhereDate The `whereDate` method may be used to compare a column's value against a date: ```php $users = Entry::query()->whereDate('created_at', '2016-12-31')->get(); ``` The `whereMonth` method may be used to compare a column's value against a specific month: ```php $users = Entry::query()->whereMonth('created_at', '12')->get(); ``` The `whereDay` method may be used to compare a column's value against a specific day of the month: ```php $users = Entry::query()->whereDay('created_at', '31')->get(); ``` The `whereYear` method may be used to compare a column's value against a specific year: ```php $users = Entry::query()->whereYear('created_at', '2016')->get(); ``` The `whereTime` method may be used to compare a column's value against a specific time: ```php $users = Entry::query()->whereTime('created_at', '=', '11:20:45')->get(); ``` ### WhereIn The `whereIn` method lets you check a field against an a given array of values: ```php Entry::query() ->whereIn('status', ['featured', 'sticky', 'special']) // [tl! ~~] ->get(); ``` You can also use the `whereNotIn` method to ensure a given field's value is not contained in a given array of values: ```php Entry::query() ->whereNotIn('status', ['draft', 'boring']) // [tl! ~~] ->get(); ``` Note: `orWhereIn` and `orWhereNotIn` are also both supported. ### WhereNull The `whereNull` method lets you check whether a field's value is null: ```php Entry::query() ->whereNull('published') // [tl! ~~] ->get(); ``` You can also use the `whereNotNull` method to check if a field's value is not null: ```php Entry::query() ->whereNotNull('published') // [tl! ~~] ->get(); ``` Note: `orWhereNull` and `orWhereNotNull` are also both supported. ## Complex where clauses Complex queries can be made by using closure-based wheres containing any of the [basic where clauses](#basic-where-clauses): ```php Entry::query() ->where(function ($query) { $query->where('status', 'featured') ->orWhere('status', 'sticky'); }) ->orWhere(function ($query) { $query->where('title', '!=', 'statamic') ->where('status', 'boring'); }) ->get(); ``` ## Conditional clauses Conditional clauses can be applied based on another condition, for example the value for an input on the HTTP request. ```php Entry::query() ->when($request->input('rad'), function ($query) { $query->where('status', 'featured') ->orWhere('status', 'sticky'); }) ->get(); ``` You can also pass a default value which will be applied when the condition fails: ```php Entry::query() ->when($request->input('rad'), function ($query) { $query->where('status', 'featured') ->orWhere('status', 'sticky'); }, function ($query) { $query->where('status', '!=', 'featured') ->where('status', '!=', 'sticky'); }) ->get(); ``` If you want to simply apply a clause when a value fails you can use `unless()`: ```php Entry::query() ->unless($request->input('rad'), function ($query) { $query->where('status', 'featured') ->orWhere('status', 'sticky'); }) ->get(); ``` ## JSON where clauses JSON values can be queries using the '->' selector: ```php Entry::query() ->where('my_field->sub_field', '!=', 'statamic') // [tl! ~~] ->get(); ``` You can query JSON arrays using `whereJsonContains()` ```php Entry::query() ->whereJsonContains('my_array_field->sub_field', 'statamic') // [tl! ~~] ->get(); ``` Or can pass an array of values. This will match if **all** of the values are found in the field. ```php Entry::query() ->whereJsonContains('my_array_field->sub_field', ['statamic', 'is', 'rad']) // [tl! ~~] ->get(); ``` If you want to check for **any** value being present, use `whereJsonOverlaps`. ```php Entry::query() ->whereJsonOverlaps('my_array_field->sub_field', ['statamic', 'is', 'rad']) // [tl! ~~] ->get(); ``` You can use `whereJsonDoesntContain()` and `whereJsonDoesntOverlap()` to query the absence of a value or values in a JSON array: ```php Entry::query() ->whereJsonDoesntContain('my_array_field->sub_field', 'statamic') // [tl! ~~] ->get(); Entry::query() ->whereJsonDoesntOverlap('my_array_field->sub_field', 'statamic') // [tl! ~~] ->get(); ``` You can use whereJsonLength method to query JSON arrays by their length: ```php Entry::query() ->whereJsonLength('my_array_field->sub_field', 1) // [tl! ~~] ->get(); ``` ```php Entry::query() ->whereJsonLength('my_array_field->sub_field', '>', 1) // [tl! ~~] ->get(); ``` Note: `orWhereJsonContains` and `orWhereJsonLength` are also both supported. ## Querying relationships You can query across [relationship fields](/fieldtypes/entries) (like `entries`, `terms`, and `users`) using `has`, `whereHas`, and `whereRelation` — mirroring Laravel's Eloquent relationship methods. :::tip Relationship querying is supported on the **Entry**, **Term**, and **User** query builders. The field passed as `$relation` must be a relationship field (`entries`, `terms`, or `users`) defined on a blueprint that belongs to the query builder. ::: ### whereHas Use `whereHas` to constrain the query to results where a relationship exists and its related records match additional conditions provided via a closure. ```php Entry::query() ->whereHas('related_posts', function ($query) { $query->where('title', 'Post 2'); }) ->get(); ``` Without a closure, `whereHas` simply checks that the relationship is not empty: ```php Entry::query()->whereHas('related_posts')->get(); ``` Note: `orWhereHas`, `whereDoesntHave`, and `orWhereDoesntHave` are also supported. ### whereRelation `whereRelation` is syntactic sugar for a `whereHas` with a single `where` clause against the related records: ```php Entry::query() ->whereRelation('related_posts', 'title', 'Post 2') ->get(); ``` It accepts the same signature as `where` — an operator and value, or a closure for more complex logic. `orWhereRelation` is also supported. ### has Use `has` to constrain the query to results where a relationship has any related records. Pair with `doesntHave` for the inverse. ```php Entry::query()->has('related_posts')->get(); Entry::query()->doesntHave('related_posts')->get(); ``` Note: `orHas` and `orDoesntHave` are also supported. :::warning A couple of things to be aware of: - **Nested relations** (e.g. `author.posts`) are not supported and will throw an `InvalidArgumentException`. - **Counting with subqueries** (e.g. `whereHas('posts', fn ($q) => ..., '>=', 10)`) is not supported and will throw an `InvalidArgumentException`. ::: ## Operators The following operators are available in [basic where clauses](#basic-where-clauses) when appropriate for a targeted field's datatype, just like SQL. | Operator | Description | | -------- | ----------- | | `=` | Equals | | `<>` or `!=` | Not Equals | | `like` | Like | | `not like` | Not Like | | `regexp` | Like Regex | | `not regexp` | Not Like Regex | | `>` | Greater Than | | `<` | Less Than | | `>=` | Greater Than Or Equal To | | `<=` | Less Than Or Equal To | ### Like & Not Like The `like` operator is used in `where` clause to search for a specified pattern in a field. `not like` is the inverse, ensuring that the results **do not** match a pattern. There are two wildcards used in conjunction with the `like` operator: - The percent sign `%` represents zero, one, or multiple characters - The underscore sign `_` represents one, single character #### Examples {.popout} #### Get all Users with a gmail email address ```php User::query() ->where('email', 'like', '%@gmail.com') ->get(); ``` #### Get all Entries where "wip" is not in the title ```php Entry::query() ->where('title', 'not like', '% wip %') ->get(); ``` #### Get all Assets with "thumbnail" in the filename. ```php Asset::query() ->where('filename', 'like', '%thumbnail%') ->get(); ``` #### Get all Users who are (probably) not doctors ```php User::query() ->where('name', 'not like', ['Dr.%', '%MD', '%M.D.']) ->get(); ``` ### Regex & Not Regex The `regex` operator is used in `where` clause to search for records where a field matches a given regular expression, while `not regex` is the inverse — ensuring that results **do not** match a regular expression. :::tip Internally, this rule uses the PHP `preg_match` function. The pattern specified should obey the same formatting required by `preg_match` and therefore also include valid delimiters. For example: `'/^.+$/i'`. ::: #### Examples {.popout} #### Find entries with Antlers expressions in content ```php Entry::query() ->where('content', 'regexp', '/{{/') ->get(); ``` #### Find all Star Trek movie subtitles but not Star Wars ```php Entry::query() ->where('collection', 'movies') ->where('title', 'not regexp', '/m | [tn]|b/') ->get(); // [tl! collapse:start] // Okay, so this regex doesn't work on any of the Star Wars // movies after Rogue One but let's not split hairs here. // This is a good example and you know it. // If we can get enough support though we can submit a // petition to Disney to rename the last 3 Skywalker sequels // so we don't need to change our regex: // The Force Awakens -> Awakening of the Force // The Last Jedi -> Near Extinction of the Jedi // The Rise of Skywalker -> Ascent of the Walker in the Sky [tl! collapse:end] ``` ### Greater Than & Less Than (Or Equal To) The `greater than` operator is used to compare two values. If the first is greater than the second, the match will be included. The `greater than or equals` operator will include exact matches. The `less than` operator is used to compare two values. If the first is less than the second, the match will be included. The `less than or equals` operator will include exact matches. #### Examples {.popout} #### Find all Users old enough to enjoy a dram of whisky in the U.S. ```php User::query() ->where('age', '>=', 21) ->get(); ``` #### Find all Pre-Y2K news ```php Entry::query() ->where('collection', 'news') ->where('date', '<', '2000') ->get(); ``` ## Ordering, Limiting, & Offsetting The `orderBy` method allows you to sort by a given field, or in random order: ```php Entry::query()->orderBy('date', 'asc')->get(); Entry::query()->orderByDesc('title')->get(); // the same as ->orderBy('title', 'desc') Entry::query()->inRandomOrder()->get(); ``` You may limit and/or skip results by using the `limit` and `offset` methods: ```php Entry::query()->offset(5)->limit(5)->get(); ``` ## Count The query builder also provides the `count` method for retrieving the number of records returned. ```php Entry::query()->count(); ``` ## Paginating Paginate results by invoking the `paginate` method on a query instead of `get`, and specifying the desired number of results per page. ```php Entry::query()->paginate(15); ``` This will return an instance of `Illuminate\Pagination\LengthAwarePaginator` that you can use to assemble the pagination style of your choice. :::tip You can [learn more about the LengthAwarePaginator](https://laravel.com/docs/13.x/pagination#paginator-instance-methods)in the Laravel docs. ::: ## Chunking By chunking down the results of a query you receive a small chunk of results that you can each pass into a closure for further processing or manipulation. Expects both a `$count` and `$callback` argument. ```php Entry::query()->chunk(25, function($entries) { // do something with each chunk }); ``` :::tip You can [learn more about chunking query results](https://laravel.com/docs/13.x/queries#chunking-results) in the Laravel docs. ::: ## Lazy streaming Lazily streaming query results allows you to define a number of results to be returned from the query, similar to [chunking](#chunking). The difference is that instead of being able to pass each chunk into a callback, you receive a `LazyCollection`. This can help in situations where you're working with large datasets while keeping the memory usage low. The chunk size for the lazy query should be at least `1` and defaults to `1000`. ```php Entry::query()->lazy(100) ``` :::tip You can learn more about [lazily streaming query results](https://laravel.com/docs/13.x/queries#streaming-results-lazily) and [LazyCollections](https://laravel.com/docs/13.x/collections#lazy-collections) in the Laravel docs. ::: ## Repository classes Head to the [Repositories Reference](reference/repositories) area for the complete list of classes and methods. ================================================ FILE: content/collections/pages/contributing.md ================================================ --- id: 55a99a3b-e40d-4033-9a70-823de8e4255f blueprint: page title: Contributing overview: | This is a guideline for contributing to Statamic, its documentation, addons, and starter kits. All of these wonderful things are hosted here in the [Statamic organization](https://github.com/statamic) on GitHub. We welcome your feedback, proposed changes, and updates to these guidelines. We will always welcome thoughtful issues and consider pull requests. --- ✨Thank you for taking the time to consider a contribution!✨ ## What you should know before contributing ### Statamic isn’t FOSS Statamic is not "Free Open Source Software". It is **proprietary** open source software. The code is open, you can use it for free, but there are limitations to how you can modify or redistribute it. Everything in this and our other repos on Github — including community-contributed code — is the property of Statamic. Here are the limitations: - You **cannot** redistribute or use Statamic as a dependency in another distributable project — open source or otherwise — without prior permission or licensing. You **can** use it in your **own** commercial or personal projects. - You **cannot** alter code or behavior related to licensing, updating, version/edition checking, or anything else that would circumvent the enforcement of our Statamic Pro business model. We want to stay in business so we can support _you_ better. - You **cannot** publicly maintain a long-term fork of Statamic. You **can** maintain a private one for your own needs, if you have them. ### How to get support For official developer support (and you own an active license), please go to [statamic.com/support](https://statamic.com/support) and we will always do our best to reply in a timely manner. **Github issues are intended for reporting and resolving bugs.** You can chat and collaborate with other developers in the community — [Discord](https://statamic.com/discord) and the [discussions area](https://github.com/statamic/cms/discussions) on GitHub are the best places to go. You will likely find many helpful folks who may be willing to help. ## How you can contribute ### Which repo? Statamic is split into a few Github repositories. Here's a quick summary of each. - [`statamic/cms`](https://github.com/statamic/cms) is the core package. It doesn't run by itself but is instead a dependency consumed by Laravel apps. **99% of the work goes on here.** - [`statamic/statamic`](https://github.com/statamic/statamic) is the starter Laravel app used to build a new site. It's an empty shell. - [`statamic/docs`](https://github.com/statamic/docs) is the Statamic documentation site that is currently running on [statamic.dev](https://statamic.dev). ### Bug reports First things first. If the bug is security related refer to our [security disclosures](#security-disclosures) procedures instead of opening an issue. Next, **please** (pretty pretty please) search through the [open issues](https://github.com/statamic/cms/issues) to see if it has already been opened. If you _do_ find a similar issue, **upvote it** by adding a 👍 [reaction](https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments). If you have relevant information to add, do so in a comment. Please don't add a `+1` comment. If no one has filed the issue yet, feel free to [submit a new one](https://github.com/statamic/cms/issues/new). Please include a clear description of the issue, follow along with the issue template, and provide as much relevant information as possible. :::tip If you are able to create a repo demonstrating an issue, we can fix it **5x faster** than if you share a code example, and **1000x faster** than if you say "it's broken plz fix it k thx byeeeeee" without even telling us the error message. ::: ### Feature requests Feature requests should be created in the [statamic/ideas](https://github.com/statamic/ideas) repository. **Please** (pretty pretty please) search through the [open issues](https://github.com/statamic/cms/issues) to see if the feature request has already been opened. If you _do_ find a similar request, **upvote it** by adding a 👍 [reaction](https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments). If you have relevant information to add, do so in a comment. Please don't add a `+1` comment. ### Security disclosures If you discover a security vulnerability, please review our [security policy](https://github.com/statamic/cms/security/policy), then report the issue directly to us from [statamic.com/support](https://statamic.com/support). We will review and respond privately via email. We do not respond to cold "do you pay bounties?" emails. ### Documentation edits Statamic's documentation lives in the [https://github.com/statamic/docs](https://github.com/statamic/docs) repository. Improvements or corrections to them can be submitted as a pull request. These usually get merged very quickly unless your grammar is bad. ### Core enhancements If you would like to work on a new feature, consider opening an issue first in [the ideas repo](https://github.com/statamic/ideas) so we can discuss it before you spend time on it. While we appreciate community contributions, we do remain selective about what features make it into Statamic itself, so don’t take it the wrong way if we recommend that you pursue the idea as an addon instead. If you're ready to start working on your feature, bug fix, or improvement, we have a [more in-depth guide to walk you through the whole thing](/contribution-guide). ### Compiled assets If you are submitting a change that will affect a compiled file, such as most of the files in `resources/css` or `resources/js`, do not commit the compiled files. Due to their large size, they cannot realistically be reviewed by our team. This could be exploited as a way to inject malicious code into Statamic. In order to defensively prevent this, all compiled files will be generated and committed by the core Statamic team. ### Code style We use [Laravel Pint](https://laravel.com/docs/master/pint#main-content) to enforce a consistent code style across the codebase. You can run it locally with `./vendor/bin/pint`. ### Control Panel translations We welcome new translations and updates! Please follow [these instructions](/cp-translations#contributing-a-new-translation) on how to contribute to Statamic's translation files. ### Pull requests Pull requests should clearly describe the problem and solution. Include the relevant issue number if there is one. If the pull request fixes a bug, it should include a new test case that demonstrates the issue, if possible. Stay rad. If you're not already rad, tell us and we will make sure you become rad. ✨ ================================================ FILE: content/collections/pages/contribution-guide.md ================================================ --- id: d0e99506-d01d-484c-884f-46dfc4dcf4c5 blueprint: page title: 'Contribution Guide' intro: 'A guide on how to contribute to the `statamic/cms` repo' --- ## Fork the repo First, you need to create a fork of the repo. A fork is a copy of the repo where you can make changes before sending them back with a request to be merged into the original repo. Head to the [cms repo][cms-repo] and click the "Fork" button at the top right. ## Clone it Once you have a fork, you can clone it on your local machine with git. It can go anywhere - you probably already have a folder where your projects live. Most people use `~/Sites/` or `~/Code`. ```shell cd Code # [tl! **] git clone https://github.com/your-username/cms.git # [tl! **] Cloning into 'cms'... remote: Enumerating objects: 86396, done. remote: Counting objects: 100% (3025/3025), done. remote: Compressing objects: 100% (1917/1917), done. remote: Total 86396 (delta 1674), reused 2078 (delta 1085), pack-reused 83371 Receiving objects: 100% (86396/86396), 33.39 MiB | 5.76 MiB/s, done. Resolving deltas: 100% (67201/67201), done. ``` ## Create a sandbox project The `cms` repo is just the Laravel package — it can't run on its own. It needs to be installed into a Laravel app. The easiest way to set this up is to install a Starter Kit. In a separate folder, create your site: ```shell cd sites # [tl! **] statamic new sandbox # [tl! **] Creating a statamic/statamic project at ./sandbox [✔] Statamic has been successfully installed into the sandbox directory. Build something rad! ``` ## Link your fork to the sandbox At this point, your sandbox app is going to be using the "real" version of Statamic. You'll need to tell it to use your local fork. In your app's `composer.json`, add a `repositories` array with a "path" repository pointing to where you cloned your fork earlier: ```json { "name": "statamic/statamic", "type": "project", "description": "Statamic", "keywords": ["statamic", "cms", "flat file", "laravel"], "require": { // [tl! collapse:start] "php": "^8.2", "laravel/framework": "^11", "laravel/tinker": "^2.9", "statamic/cms": "^5.0" }, // [tl! collapse:end] "require-dev": { // [tl! collapse:start] "barryvdh/laravel-debugbar": "^3.8.1", "fakerphp/faker": "^1.23", "laravel/pint": "^1.13", "laravel/sail": "^1.26", "mockery/mockery": "^1.6", "nunomaduro/collision": "^8.0", "phpunit/phpunit": "^11.0", "spatie/laravel-ignition": "^2.4" }, // [tl! collapse:end] "autoload": { // [tl! collapse:start] "psr-4": { "App\\": "app/", "Database\\Factories\\": "database/factories/", "Database\\Seeders\\": "database/seeders/" } }, // [tl! collapse:end] "autoload-dev": { // [tl! collapse:start] "psr-4": { "Tests\\": "tests/" } }, // [tl! collapse:end] "scripts": { // [tl! collapse:start] "pre-update-cmd": [ "Statamic\\Console\\Composer\\Scripts::preUpdateCmd" ], "post-autoload-dump": [ "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", "@php artisan package:discover --ansi", "@php artisan statamic:install --ansi" ], "post-root-package-install": [ "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" ], "post-create-project-cmd": [ "@php artisan key:generate --ansi", "@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"" ], "post-update-cmd": [ "@php artisan vendor:publish --tag=laravel-assets --ansi --force" ] }, // [tl! collapse:end] "extra": { // [tl! collapse:start] "laravel": { "dont-discover": [] } }, // [tl! collapse:end] "config": { // [tl! collapse:start] "optimize-autoloader": true, "preferred-install": "dist", "sort-packages": true, "allow-plugins": { "pestphp/pest-plugin": true, "php-http/discovery": true, "pixelfear/composer-dist-plugin": true } }, // [tl! collapse:end] "minimum-stability": "dev", "prefer-stable": true, "repositories": [ // [tl! focus:start] { "type": "path", "url": "/path/to/cms" } ] // [tl! focus:end] } ``` Next, require the branch of `cms` you checked out: ```shell composer require "statamic/cms 6.x-dev" ``` (We'll go into more detail in a moment on what constraint should be used there.) In the output, you should see it symlinks the `cms` directory to your fork: ```shell ./composer.json has been updated Running composer update statamic/cms > Statamic\Console\Composer\Scripts::preUpdateCmd Loading composer repositories with package information Updating dependencies Lock file operations: 0 installs, 1 update, 0 removals - Upgrading statamic/cms (v5.7.3 => 6.x-dev) # [tl! focus] Writing lock file Installing dependencies from lock file (including require-dev) Package operations: 0 installs, 1 update, 0 removals - Removing statamic/cms (v5.7.3) - Installing statamic/cms (6.x-dev): Symlinking from /path/to/cms # [tl! focus] - Downloading statamic/cms (dist) Failed to download Generating optimized autoload files > Illuminate\Foundation\ComposerScripts::postAutoloadDump > @php artisan package:discover --ansi Discovered Package: ajthinking/archetype Discovered Package: barryvdh/laravel-debugbar Discovered Package: intervention/image Discovered Package: laravel/sail Discovered Package: laravel/tinker Discovered Package: nesbot/carbon Discovered Package: nunomaduro/collision Discovered Package: nunomaduro/termwind Discovered Package: rebing/graphql-laravel Discovered Package: spatie/laravel-ignition Discovered Package: statamic/cms Discovered Package: wilderborn/partyline > @php artisan statamic:install --ansi Discovering addons. Publishing [statamic] assets. Publishing [statamic-cp] assets. Publishing [statamic-frontend] assets. Compiled views cleared successfully. Application cache cleared successfully. 97 packages you are using are looking for funding. Use the `composer fund` command to find out more! ``` You can confirm it by checking the path to the package: ```shell composer show statamic/cms --path # [tl! focus] statamic/cms /path/to/cms ``` ## Use an appropriate branch Be sure to work on a new, dedicated branch for your Pull Request. Among other things, it'll make it easier for the Statamic team to push minor changes if necessary (like fixing typos, code style, tweaks, and so on). We request that you add "feature" or "fix" in the branch name so it's easier to understand the intent of your PR. ```shell git checkout -b feature/new-thing git checkout -b fix/issue-9999 ``` :::warning Psst! When requiring the `cms` package, it's important to require the appropriate constraint. If you don't use the right one, Composer may decide to use the _real_ `cms` package, and you'll be left wondering why your code changes aren't appearing. ::: If the branch is numeric then you need to require `BRANCH.x-dev` (e.g. a branch named `6.x` should use a constraint of `6.x-dev`). Otherwise, you'll need to use `dev-BRANCH` (e.g. a branch named `feature/mybranch` should use a constraint of `dev-feature/mybranch`). :::tip Once you've done the initial symlink, you can change `cms` branches freely. However once again, be aware if you do a `composer update` or `require`, you may end up with a live version of `cms`. ::: ## Dealing with assets If your contribution involves Control Panel assets - Stylesheets, JavaScript, or Vue components - you'll need to compile them and have them used by your sandbox app. You can do this with another symlink. In your sandbox, delete the `public/vendor/statamic/cp` directory, which should have been created when you initially created the site. Compile the assets within the `cms` repo. ```shell cd cms npm ci npm run dev # or npm run build ``` The assets will be compiled into `cms/resources/dist`. You can now symlink them into your sandbox: ```shell cd sandbox ln -s /path/to/cms/resources/dist public/vendor/statamic/cp ``` :::tip **Do not attempt to commit any compiled code.** They should already be gitignored, and will be automatically recompiled at release time. ::: ## Commit code Now you're ready to actually write code. If you're writing tests, you can run the test suite inside the `cms` repo. If you want to manually test or use the package, you can do it in through your sandbox. Any changes you make to the code in your `cms` repo will be reflected in your sandbox app which you can see in the browser. Once you're done, you should push your branch to Github. ```shell git push --set-upstream origin HEAD # [tl! **] Enumerating objects: 5, done. Counting objects: 100% (5/5), done. Delta compression using up to 8 threads Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 309 bytes | 309.00 KiB/s, done. Total 3 (delta 2), reused 0 (delta 0), pack-reused 0 remote: Resolving deltas: 100% (2/2), completed with 2 local objects. remote: remote: Create a pull request for 'feature/new-thing' on GitHub by visiting: # [tl! **] remote: https://github.com/your-username/cms/pull/new/feature/new-thing # [tl! **] remote: To https://github.com/your-username/cms.git * [new branch] HEAD -> feature/new-thing Branch 'feature/new-thing' set up to track remote branch 'feature/new-thing' from 'origin'. ``` ## Create the Pull Request :::best-practice When creating a pull request that introduces a **new feature or changes current behavior**, please open an issue referencing your PR on the [statamic/docs](https://github.com/statamic/docs/issues/new) repo. No need to write the docs yourself. We'll take care of that for you. Any hints or bullet points are appreciated though! ::: In the output from pushing your branch above, it'll give you a link to create the pull request. If you missed it, no problem. Just head over to `statamic/cms` and you should see a banner waiting for you. ![](/img/guides/contribution-guide/pull-request-banner.png) Click through there and you'll be taken to a form where you can describe what's being contributed. ![](/img/guides/contribution-guide/pull-request-form.png) Please be as thorough as possible. Explain what's being added, what it fixes, list any relevant issues or discussions, and explain how we can test out the changes. ## Cleaning Up Once the PR is resolved, either by being merged or closed, you're free to delete the branch or even the entire fork. If your PR was merged, you'll be mentioned in the next release's changelog where you will live in infamy. ✨ ![](/img/guides/contribution-guide/release.png) [cms-repo]: https://github.com/statamic/cms ## Extra Credit If you're a frequent contributor, you may consider permanently setting up the Composer path repository. Instead of adding `repositories` key into your sandbox's `composer.json` every time, you can add it to your global Composer `~/.composer/config.json`. ```json { "repositories": [ { "type": "path", "url": "/path/to/cms", "canonical": false } ] } ``` ================================================ FILE: content/collections/pages/control-panel.md ================================================ --- id: a9acf949-e23d-42ab-8bc2-21032c98df9a title: 'Control Panel' blueprint: link redirect: url: '@child' status: 301 --- ================================================ FILE: content/collections/pages/controllers.md ================================================ --- title: Controllers intro: Controllers group related Laravel request handling logic into single classes stored in the `app/Http/Controllers/` directory. Use them to build frontend areas or full custom apps, the Laravel way! id: 5e848460-9bbc-449e-8edd-182d918163ff blueprint: page --- ## Overview Statamic is a package sitting _inside_ a standard Laravel application, giving you the freedom to create your own routes, map them to controllers, and build your own custom application and business logic outside of Statamic's feature set. Anything you can do in Laravel you can do here. Because you're using Laravel. You just _also_ have access to all of Statamic's capabilities and features. ## Routes [Routes][laravel-routes] are defined in `routes/web.php`. :::tip These explicitly defined routes will take precedence over Statamic routes and URL patterns. Keep this in mind. ::: For example, you can map a `GET` request to `yoursite.com/example` to the `index` method in the `app/Http/Controllers/ExampleController.php` file like this: ``` php use App\Http\Controllers\ExampleController; Route::get('example', [ExampleController::class, 'index']); ``` ## Basic controller In your controller, render views like you would in a regular Laravel app: ``` php 'Example Title' ]; return view('myview', $data); // resources/views/myview.blade.php } } ``` :::tip You can generate a controller with the following [Artisan command](https://laravel.com/docs/artisan): ```shell php artisan make:controller ExampleController ``` ::: ## Antlers views Returning `view('myview')` _will_ render the `myview.antlers.html` view. To take advantage of Statamic's standard template-injected-into-a-layout behavior, return a `Statamic\View\View` instance instead of a regular Laravel one. ``` php public function index() { return (new \Statamic\View\View) ->template('myview') ->layout('mylayout') ->with(['title' => 'Example Title']); } ``` Now, `myview` will be injected into `mylayout`'s `template_content` variable. Anything provided to `with` (eg. `title`) will be available in both views. If you want to make an entry's content available in your view, you can use the `cascadeContent` method: ``` php // app/Http/Controllers/MySpecialController.php public function index() { $entry = Entry::whereCollection('pages') ->where('slug', 'special-page') ->where('published', true) ->first(); return (new \Statamic\View\View) ->template('myview') ->layout('mylayout') ->cascadeContent($entry); } ``` ## Related reading - [Laravel controllers][laravel-controllers] - [Laravel routes][laravel-routes] - [Antlers](/antlers) [laravel-controllers]: https://laravel.com/docs/controllers [laravel-routes]: https://laravel.com/docs/routing ================================================ FILE: content/collections/pages/core-concepts.md ================================================ --- id: 24a9c9d8-d607-4117-9806-738668c173cd blueprint: page title: 'Core Concepts' intro: 'Statamic is opinionated software. Understanding the principles we follow and apply to the way we build features will help you learn Statamic faster.' template: page breadcrumb_title: Overview --- ## Statamic is opinionated but configurable Statamic is an **opinionated platform**. We set defaults to match the most common use cases and implement patterns that help speed up your workflow, enforce consistency, and make it easy to share code between projects. Following these conventions will make it easier to switch between different Statamic projects because you'll know right where everything is and what it's called. Sometimes these conventions don't fit your project, or maybe you're perfectly happy with your own way of doing things. That's fine — our conventions can usually be configured, overridden, or ignored. :::tip **But don't break convention unless you have a really, really good reason.** Like integrating Statamic with an existing Laravel app or when porting a site from another platform. ::: A good example of this is the decision on whether to use [Blade](/blade) as the template language over [Antlers](/antlers). Antlers is deeply integrated with Statamic and can handle the responsibilities of both Blade _and_ Controllers right in your template. If you choose to use Blade templates, you will also have access to our Antlers Blade components, or you can use controllers and fetch data more of the traditional MVC way. :::best-practice Do your best to maintain a project `README.md` with anything you do to override Statamic's default behavior just in case you hand the site off to someone else. ::: ## Statamic is flat _first_ Statamic has the ability to adapt to any data storage mechanism, from relational databases like MySQL and Postgres, to NoSQL solutions like MongoDB and Redis, and more. This feature is called [Repositories](/extending/repositories). However, these solutions **add complexity** and should only be used when necessary, most often for scaling for large amounts of data (tens of thousands of records) or high volume traffic. Statamic operates in flat file mode by default, which reduces complexity compared to many other architectures, and opens up many possibilities, including: - End-to-end **version control**. - The ability to write and manage content, configs, and templates all **right in your code editor**. - The ability to copy & paste or share anything between sites. - **Ridiculously simple** deployment and load balancing scenarios. As your site scales, you can choose to move from the flat file driver to one best suiting your needs. **Deferring this decision prevents premature optimization and technical debt.**
    Premature Optimization comic by XKCD
    Let's be honest. We've all done this.
    ## The content schema is up to you It's completely up to you how to organize your content. You pick the field names, you pick how to organize entries into different collections. You pick what to name your taxonomies, what the URL patterns should be, and so on. We believe that forcing every site to use the same content model is nothing short of a crime. With 40+ different built-in fieldtypes, there are many perfectly reasonable ways to structure and manage your content. If you like the "one big field" approach with all your content and markup in one chunk, build your site around the [Bard](/fieldtypes/bard) fieldtype and add custom Set blocks with other fieldtypes to get fancy. Or if you prefer to break everything up into small, discrete, optional fields, showing and hiding things as needed, you can do that too (you should check out [conditional fields](/conditional-fields)). ## You bring the HTML Statamic doesn't start with a design or HTML you're expected to use or hack apart. It doesn't include any CSS or JavaScript either. All of that is up to you (or a [Starter Kit](/starter-kits)) to provide. Every Statamic site — just like every fingerprint and person in the world — is unique. This is not a platform for the generic web. This is a tool used to build anything you can imagine. Because of this, **most Statamic projects need to involve a developer** for at least _part_ of the process. It's not very "no-code" friendly solution to assemble. But once the site is built and all the collections and blueprints configured, just about **anyone can handle maintaining the site**. ## Keep it simple Statamic does its best to take a "start simple and add things as needed" approach to features and settings, in contrast to other platforms that take a "everything is included and rip out what you don't want" approach. This means that Statamic doesn't do everything right out the box. We find it's much better in the long run to turn on the things you need, enable features you plan to use, and name things the way you want, than to spend precious time clicking about the control panel disabling everything you'll never end up needing, or explaining to a client why the button they clicked doesn't do what they expect. :::tip If many of the sites you build share a common set of features, collections, blueprints, and/or templates, consider turning them into a [Starter Kit](/starter-kits) and make it your boilerplate to kickstart new projects. ::: ## Statamic is a box of Lego bricks You **may** be used to content management systems and platforms that have a long list of explicit pre-built features, or plugins that provide these features, like photo galleries, hero images, and so on. Statamic takes a different approach, that when combined with our "Bring Your Own HTML" core approach, enables you to build _almost anything_, like a box full of LEGO bricks. **Want to build a photo gallery?** Add an Assets field that lets you select multiple images, and then loop through the selected images and render thumbnails on the fly with the Glide tag, and link to the full resolution image. **Need an image slider?** Add an Assets field, select multiple images, and pass the list of images into any number of open source image slider components available on Github. **Got a Hero Image?** Use an Assets and text field and render the text on top of the `background-image` of your choosing. And just like with Lego bricks, it hurts really bad to step on Statamic barefooted. Hopefully you get the idea and see how you can solve almost any challenge with core fieldtypes and some HTML. ## The Control Panel can be optional You should be able to do everything (and more) without ever logging into the Control Panel. Granted, it _does_ tend to make some of the more complicated things easier (like creating relationships, discovering all possible options for a given setting, and rearranging pages in a nav tree), but we love efficiency and your editor is a great place to find it. Plus, you can easily tap into the power of AI in your code editor to manipulate every little bit of your site and content. ================================================ FILE: content/collections/pages/cp-navigation.md ================================================ --- title: Extending CP Navigation template: page updated_by: 42bb2659-2277-44da-a5ea-2f1eed146402 updated_at: 1569347202 intro: The Control Panel navigation is quite customizable. Addons can add their own sections, pages, and subpages, as well as remove and modify existing ones. id: 785ffa10-8b63-44b1-9da3-3837250cacbe --- :::tip This page refers to the Control Panel's side-bar navigation. Not to be confused with ["Navs"](/navigation), where you can create trees to be used for the front-end of your site. ::: ## Overview ### Customization vs Extension Statamic offers the ability for end users to [customize their CP nav via a user-friendly preferences GUI](/customizing-the-cp-nav), but this page is focused on Statamic's PHP API for extending the CP nav within an addon. ### Registering Your Extension Every nav item is represented by a `NavItem` object, which has a [full API](#the-navitem-class) for [adding](#adding-items), [removing](#removing-items), and [modifying](#modifying-items) items. You may register your nav extensions in the `boot()` method of a service provider. ## Adding Items Let's assume we're creating a Store addon, and want to add a `Store` nav item to the `Content` section of the navigation. To add this item, we'll add the following code to our service provider's `boot()` method: ```php use Statamic\Facades\CP\Nav; public function bootAddon() { Nav::extend(function ($nav) { $nav->content('Store') ->route('store.index') ->icon('shopping-cart'); }); } ``` The `content()` method there is a [magic method](http://php.net/manual/en/language.oop5.magic.php), and the name of method defines the section name that will be used. If we need to display special characters in our section name, we can `create()` the nav item and explicitly define the section name: ```php Nav::extend(function ($nav) { $nav->create('Store') ->section('Jack & Sons Inc.') ->route('store.index') ->icon('shopping-cart'); }); ``` The `icon()` method accepts the name of an [icon included in Statamic](https://ui.statamic.dev/?path=/docs/components-icon--docs#available-icons), or an SVG string containing a custom icon (be sure to use `fill="currentColor"`): ```php Nav::extend(function ($nav) { $nav->create('Store') ->section('Jack & Sons Inc.') ->route('store.index') ->icon(''); }); ``` :::tip Note that the `Nav` facade is `Statamic\Facades\CP\Nav`. There's another Nav facade _without_ the CP namespace, and it's for the front-end ["Navs"](/navigation) feature. ::: ## Adding Children Maybe we have `Products` and `Orders`, which we want to display as children under the `Store` item. To do this, we'll add a `children()` call to the parent nav item: ```php Nav::extend(function ($nav) { $nav->content('Store') ->route('store.index') ->icon('shopping-cart') ->children([ 'Products' => cp_route('store.products.index'), 'Orders' => cp_route('store.orders.index') ]); }); ``` If we need to customize our child items further, we can use object notation. For example, maybe we would like to authorize whether the user `can()` see these nav items: ```php Nav::extend(function ($nav) { $nav->content('Store') ->route('store.index') ->icon('shopping-cart') ->can('view store') ->children([ $nav->item('Products')->route('store.products.index')->can('view products'), $nav->item('Orders')->route('store.orders.index')->can('view orders') ]); }); ``` We can also defer the creation of children until render time by passing a closure. For example, if we're dynamically hitting a data store to generate our children, we can use a closure to avoid the performance hit unless the navigation actually needs to render the children: ```php Nav::extend(function ($nav) { $nav->content('Store') ->url('store') ->icon('shopping-cart') ->children(function () { return ProductType::hasPublished()->get()->map(function ($type) { return Nav::item($type->name)->url($type->url); }); }); }); ``` ## Removing Items To remove an item, we may specify the section and item name: ```php Nav::extend(function ($nav) { $nav->remove('Content', 'Store'); }); ``` To remove a child of an item, we can pass a third param to specify the child's name: ```php Nav::extend(function ($nav) { $nav->remove('Content', 'Collections', 'Products'); }); ``` To remove an entire section, we only need to specify the section name: ```php Nav::extend(function ($nav) { $nav->remove('Content'); }); ``` ## Modifying Items We can access any existing item using the same syntax as described above [when adding items](#adding-items). We can even modify native Statamic nav items. For example, maybe we wish to change the icon for the `Collections` item in the `Content` section of the nav: ```php Nav::extend(function ($nav) { $nav->content('Collections') ->icon('coins'); }); ``` The `content()` method there is a [magic method](http://php.net/manual/en/language.oop5.magic.php), which performs a `findOrCreate()` under the hood. If the nav item is found, we can then chain on any modifications to be applied to the item. If our section name contains any special characters, we can perform an explicit `findOrCreate()`: ```php Nav::extend(function ($nav) { $nav->findOrCreate('Jack & Sons Inc.', 'Store') ->icon('coins'); }); ``` ## The NavItem Class Each item you see in the navigation is an instance of the `Statamic\CP\Navigation\NavItem` class. Each top level instance within a section may contain its own collection of `NavItem` children. ### Basic API The code examples above demonstrate how to [add](#adding-items), [modify](#modifying-items), and [remove](#removing-items) `NavItem` objects. Once you have a `NavItem` object, the following chainable methods are available to you: | Method | Parameters | Description | | :--- | :--- | :--- | | `name()` | `$name` (string) | Define item name. | | `section()` | `$section` (string) | Define section name. | | `route()` | `$name` (string), `$params` (mixed, optional) | Define a route automatically prefixing with `statamic.cp.` | | `url()` | `$url` (string) | Define a URL instead of a route. A string without a leading slash will be relative from the CP. A leading slash will be relative from the root. You may provide an absolute URL. | | `icon()` | `$icon` (string) | Define icon. | | `children()` | `$children` (array\|collection\|closure) | Define child items. | | `can()` | `$ability` (string), `$params` (mixed, optional) | Define authorization. | | `view()` | `$view` (string) | Define custom view. | ## Breadcrumbs Breadcrumbs are displayed at the top of the Control Panel, making it easy to understand where you are in the navigation. Statamic automatically generates these breadcrumbs using the CP navigation. It supports a couple of additional options which can be set using the `extra()` method on a `NavItem`: ```php Nav::extend(function ($nav) { $nav->content('Store') ->route('store.index') ->icon('shopping-cart') ->extra([ // [tl! focus:start] 'breadcrumbs' => [ // Create button 'create_label' => 'Create Product', 'create_url' => cp_route('store.products.create'), // Configure button 'configure_url' => cp_route('store.settings'), ], ]); // [tl! focus:end] }); ``` You may also push additional breadcrumbs from your controller, using the `Breadcrumbs` class: ```php use Statamic\CP\Breadcrumbs\Breadcrumb; use Statamic\CP\Breadcrumbs\Breadcrumbs; Breadcrumbs::push(new Breadcrumb( text: 'Sneakers', url: cp_route('store.products.category', 'sneakers'), icon: 'sneakers', links: [ ['text' => 'T-shirts', 'icon' => 't-shirts', 'url' => cp_route('store.products.category', 't-shirts')], ['text' => 'Socks', 'icon' => 'socks', 'url' => cp_route('store.products.category', 'socks')], ], createLabel: 'Create Category', createUrl: cp_route('store.products.category.create'), )); ``` ================================================ FILE: content/collections/pages/cp-translations.md ================================================ --- title: 'Control Panel Translations' nav_title: Translations intro: "Statamic's Control Panel is currently available in 28 languages. We always welcome new translations!" blueprint: page id: 79129d32-3f7c-4215-b6b1-21a2fccafa8d --- ## Configuration Set which language you want to use by default in `config/app.php`. You may also choose a fallback locale in case new content and strings are added to the control panel before an accompanying translation has been updated. ``` php 'locale' => 'es', 'fallback_locale' => 'en', ``` ### Per-user override You can override the translation locale on a per-user basis by setting `locale: {code}` in a given user's preferences (their YAML record). ``` yaml # users/nosmo.king@example.com name: Nosmo King super: true preferences: locale: en ``` ### Available Translations | Language | Code | |---------------------|---------| | Arabic | `ar` | | Azerbaijani | `az` | | Czech | `cs` | | Danish | `da` | | German | `de` | | German (Swiss) | `de_CH` | | English | `en` | | Spanish | `es` | | Estonian | `et` | | Persian | `fa` | | French | `fr` | | Hungarian | `hu` | | Indonesian | `id` | | Italian | `it` | | Japanese | `ja` | | Malay | `ms` | | Norwegian | `nb` | | Dutch | `nl` | | Polish | `pl` | | Portuguese | `pt` | | Portuguese (Brazil) | `pt_BR` | | Russian | `ru` | | Slovene | `sl` | | Swedish | `sv` | | Turkish | `tr` | | Ukrainian | `uk` | | Vietnamese | `vi` | | Chinese | `zh_CN` | | Chinese (Taiwan) | `zh_TW` | _Translations are community contributed so you may find them to be incomplete shortly after an update._ ## Translations not covered by Statamic Although Statamic's translations cover *most* of the strings in the Control Panel, there are a couple of places where Statamic will fallback to your application's translations. One example of this is on Statamic's authentication pages. Since it's using Laravel's built-in authentication under the hood, translations for any validation errors will be pulled from your app's `lang` or `resources/lang` directory. To save you manually translating Laravel's strings yourself, you can copy the necessary translations from the community-driven [Laravel-lang](https://github.com/Laravel-Lang/lang/tree/main/locales) repository into your application. ## Contributing a new translation There are 4 steps. 1. Clone [`statamic/cms`](https://github.com/statamic/cms) locally 2. Run `composer install` 3. Generate a new translation from source files 4. Translate new message files in `lang` 5. Add the language to the [array in CorePreferences](https://github.com/statamic/cms/blob/cce7045e3f0ff418ee6e0a982a3830d604c6b64c/src/Preferences/CorePreferences.php#L56-L82) so it's selectable 6. Commit changes and submit a PR ### Generating translation files Run the `translator generate` command in the `statamic/cms` project, along with the new language code as an argument. This will generate empty JSON and PHP files in `lang` ready to be translated into the locale of your choice. You can specify a short 2 character language code (`es`) or the full 4 character regional code (`es_MX`). _It is recommended that you comply to the [`language code standard`](https://www.science.co.il/language/Codes.php)._ ``` shell php translator generate eo ``` - The JSON file contains all the "short strings" established on the fly with the translation helpers, e.g. `__('Cowabunga')`. - The PHP files contain longer strings and are well organized by section of the control panel. - Translatable strings can contain a `|` to separate singular and plurals. - Translatable strings can contain the `:something` format to indicate a variable. ``` files theme:serendipity-light lang/ |-- eo/ | |-- markdown.php | |-- messages.php | |-- permissions.php | |-- validation.php |-- eo.json ``` :::tip This command will also update existing files with any changes from recent Statamic releases. ::: #### Using Google translate You can get a translation kickstarted with the Google API by passing your API key. ``` shell php translator generate eo --key=abc123 ``` ### Using the reviewer Running the `translator review` command will loop through all the translations showing you the key, the English phrase, and new translated phrase for proofreading. You can enter new translations during this process. You can also use this command to gather new or changed translatable strings after a Statamic update. ``` shell php translator review eo messages ``` ================================================ FILE: content/collections/pages/creating-a-starter-kit.md ================================================ --- id: 9c703f43-30de-4f65-98bb-2b89f80012b7 title: 'Creating a Starter Kit' template: page blueprint: page intro: 'Thinking of creating your own Statamic Starter Kit? Here''s everything you need to know to get started.' nav_title: Creating --- ## Overview Starter Kit development happens within a real instance of Statamic, just like developing any other Statamic site using your normal, preferred workflows. A released Starter Kit package contains **only** the files relevant to the Kit itself, and not a full Statamic/Laravel instance. Our Import/Export tools will allow you to maintain **only those relevant files**, without having to worry about maintaining the Statamic and underlying Laravel instances as they get updated over time. The **Export** command will export all the files and directories you've created or configured to a new location. It's this directory that becomes the package, and is the thing you should version control, not the sandbox instance. For example, maybe you are creating a pre-built, theme-style Starter Kit, the high-level workflow might look like this: 1. Create a new Statamic project. 2. Initialize it as a Starter Kit: ```shell php please starter-kit:init ``` 3. Develop the theme as you normally would. 4. Export the theme to a separate repo for redistribution. ``` shell php please starter-kit:export ../kung-fury-theme ``` 5. Publish to [Github](https://github.com/), [Gitlab](https://gitlab.com/), or [Bitbucket](https://bitbucket.org/). 6. Install into new Statamic projects. ``` shell php please starter-kit:install the-hoff/kung-fury-theme ``` ## Creating the Starter Kit project The first step is to [create a new Statamic project](/installing#creating-a-new-statamic-project). This is essentially a throwaway sandbox that you will use to develop and test your Starter Kit. Run the `init` command to generate the appropriate files. ```shell php please starter-kit:init ``` This command will create and wire up a `package` directory, which represents the eventual Starter Kit repository's root directory. ## The Starter Kit package Starter Kits are installed via Composer. You can control the package's contents via the `package` directory, which will be the exported repository's root directory. At a minimum, your `package` directory needs a `starter-kit.yaml` and a `composer.json` file. ``` files theme:serendipity-light app/ content/ config/ package/ #[tl! ++] composer.json #[tl! ++] starter-kit.yaml #[tl! ++] public/ resources/ composer.json ``` You can also include other files like a `README.md`, etc. as well. Everything you put into this `/package` folder will be exported to your repository's root directory. ``` files theme:serendipity-light package/ composer.json starter-kit.yaml README.md #[tl! ++] ``` :::tip If you want a separate `README.md` to be installed when the end user installs your Starter Kit into their app, you can export the `README.md` at the root of your development sandbox by adding it to [export_paths](http://docs.test/starter-kits/creating-a-starter-kit#exporting-paths). ::: Finally, if you plan to [make your Starter Kit updatable](#making-starter-kits-updatable), you should require this as a path repository. ```json { "name": "statamic/statamic", "require": [ "the-hoff/kung-fury-theme": "dev-master" // [tl! ++] ], "repositories": [ // [tl! ++:start] { "type": "path", "url": "package" } ] // [tl! ++:end] } ``` :::tip This `package` folder can be automatically scaffolded and wired up in your composer `repositories` by [running the init command](#creating-the-starter-kit-project) to create your Starter Kit project. ::: ## Exporting When ready to export your Starter Kit, run the following command: ``` shell php please starter-kit:export {export_repo_path} ``` This will copy and arrange the appropriate files into the given directory that will be used as a distributable on GitHub, GitLab, Bitbucket, Composer, etc. :::tip Think of the exported directory similar to a compiled assets directory when using a build tool like Vite. You generate files into this directory and shouldn't touch it manually. ::: ### Exporting Paths Any files that you modify on your site that you intend to be installed into a Statamic project should be marked as `export_paths` in your `starter-kit.yaml` file. For example, the following config would tell Statamic to export sample content, along with related assets, config, blueprints, css, views, and front-end build config out for distribution on the Statamic Marketplace. ``` yaml export_paths: - content - config/filesystems.php - config/statamic/assets.php - resources/blueprints - resources/css/site.css - resources/views - public/assets - public/css - package.json - tailwind.config.js - webpack.mix.js ``` Anything not configured in your `starter-kit.yaml` **will not be exported**. This way you don't have to maintain a full Statamic site, or any bootstrap code that is unrelated to your Starter Kit. Once your export paths are configured, re-run the above `starter-kit:export` command. Your files should now be available at your new export repo path. #### Clearing stale files Re-running the export command will overwrite files in your export repo, but it _won't_ remove files that you've since deleted or renamed in your sandbox project. To wipe your configured `export_paths` in the destination before re-exporting, pass the `--clear` flag: ``` shell php please starter-kit:export ../kung-fury-theme --clear ``` Since this is destructive, it's opt-in. Review the resulting git diff in your export repo before committing — anything outside your `export_paths` (like `.github`, the stubbed `composer.json`, etc.) is left untouched, so you can discard any overzealous changes before pushing. ### Exporting dependencies If you wish to bundle any of your installed Composer dependencies with your Starter Kit, just `composer require` them in your sandbox project as you would into any app, then add them under a `dependencies` array in your `starter-kit.yaml` config file: ``` yaml dependencies: - statamic/ssg ``` The exporter will automatically detect the installed versions and whether or not they are installed as dev dependencies, and export accordingly. When [installing the Starter Kit](#installing-a-starter-kit), composer will install with the same version constraints as you had installed in your sandbox project during development. ## Optional modules You may also present an optional set of Starter Kit files, nested under `modules` in your `starter-kit.yaml` config file. For example, here we'll configure an opt-in `seo` module. ```yaml modules: seo: dependencies: - statamic/seo-pro ``` This presents a choice to the user, to confirm whether or not to install this module.
    The user can confirm whether or not to install the `seo` module
    These modules are compatible with the same config options that you use at the top level of your config file (ie. `export_paths`, `dependencies`, etc.). ```yaml modules: seo: export_paths: - resources/css/seo.css dependencies: - statamic/seo-pro ``` ### Customizing Prompt Text If you don't like the default prompt text, you can customize it with custom `prompt` config. ```yaml modules: seo: prompt: 'Would you like some awesome SEO with that!?' dependencies: - statamic/seo-pro ```
    Starter Kit custom prompt text
    Would you also like fries with that?
    ### Customizing prompt default value Setting `default: true` will ensure the module is installed by default if the user spams the enter key through the prompt, or the Starter Kit is installed non-interactively. ```yaml modules: seo: default: true dependencies: - statamic/seo-pro ``` ### Skipping confirmation Or maybe you wish to skip the user prompt and always install a given module, using modules to better organize larger Starter Kit configs. To do this, simply set `prompt` to false. ```yaml modules: seo: prompt: false ``` ### Selecting between modules You may find yourself in a situation where you want the user to select only one of multiple module options. To do this, you may nest multiple module configs under an `options` object. ```yaml modules: js: options: vue: export_paths: - resources/js/vue.js react: export_paths: - resources/js/react.js mootools: export_paths: - resources/js/mootools.js ```
    Starter Kit select module
    ### Customizing select module prompt text Of course, you can also customize `prompt` text, the first 'No' `skip_option` text, as well as each option `label`, as you see fit. ```yaml modules: js: prompt: 'Would you care for some JS?' skip_option: 'No, thank you!' options: vue: label: 'VueJS' export_paths: - resources/js/vue.js react: label: 'ReactJS' export_paths: - resources/js/react.js mootools: label: 'MooTools (will never die!)' export_paths: - resources/js/mootools.js ```
    Customizing Starter Kit select module
    🐮🐮🐮
    ### Customizing select module default value Setting a `default` value will ensure a specific module option is installed by default if the user spams the enter key through the prompt, or the Starter Kit is installed non-interactively. ```yaml modules: js: prompt: 'Would you care for some JS?' default: vue # ... ``` ### Disabling select module skip option If you want to force the user to select a module option, you can set `skip_option: false` to disable the 'No' skip option. ```yaml modules: js: prompt: 'Would you care for some JS?' skip_option: false # ... ```
    Starter kit disable skip option
    ### Nesting modules Finally, you can also nest modules where it makes sense to do so. Simply nest a `modules` object within any module. ```yaml modules: seo: prompt: 'Would you like some awesome SEO with that!?' dependencies: - statamic/seo-pro modules: sitemap: prompt: 'Would you like additional SEO sitemap features as well?' dependencies: - statamic/seo-pro-sitemap ``` In this example, the second `sitemap` module prompt will only be presented to the user, if they agree to installing the parent `seo` module. ## Post-install hooks You may run additional logic after the Starter Kit is installed. For example, maybe you want to output some information. To do so, you can create a `StarterKitPostInstall.php` file in the root of your Starter Kit. It should be a simple non-namespaced class with a `handle` method. You will be provided with an instance of the command so you can output lines, get input, and so on. ```php line('Thanks for installing!'); } } ``` :::tip Statamic will automatically export this file if it exists. You don't need to add it to export_paths. ::: ## Publishing a Starter Kit Once exported, you will need to update the `name` property in the `composer.json` created at your specified export repo path. It should match your Composer/GitHub {Organization}/{Repo_Name} exactly. ``` json { "name": "the-hoff/kung-fury-theme", "extra": { "statamic": { "name": "Kung Fury Theme", "description": "Kung Fury Theme Starter Kit" } } } ``` Now create a `README.md` file and push to [Github](https://github.com/), [Gitlab](https://gitlab.com/), or [Bitbucket](https://bitbucket.org/), as you would any PHP package. This is all that is required to publish a free Starter Kit! :::tip Unlike addons, you are not required to register on [Packagist](https://packagist.org/). ::: If you would like to share your Starter Kit, receive more exposure, or would like to charge for your Kit, you should [publish it to the Statamic Marketplace](#publishing-to-the-marketplace). ## Publishing to the Marketplace Once your Starter Kit is ready for the world, you can publish it on the [Statamic Marketplace](https://statamic.com/marketplace) where it can be discovered by others. Before you can publish your Starter Kit, you'll need a couple of things: - A [Statamic Seller Account](https://statamic.com/creator) - A connected [Stripe](https://stripe.com) account _only if_ you're planning to sell your Starter Kits. In your seller dashboard, you can create a product. There you'll be able to link your Composer package that you created on Packagist, choose a price, write a description, and so on. Products will be marked as drafts that you can preview and tweak until you're ready to go. Once published, you'll be able to see your Starter Kit on the Marketplace and within the Starter Kits area of the Statamic Control Panel. ## Installing from a local repo To test install your Starter Kit from your local exported repo, you can add the repo's local path to your global Composer `config.json` file as a repository: ```json { "repositories": [ { "type": "path", "url": "/Users/hasselhoff/kung-fury-theme" } ] } ``` :::tip If you are not sure where your `config.json` is located, run `composer config --global home` to see the location of your global Composer config. ::: With your repo's local path added to your `config.json`, you should now be able to install using the `--local` cli option: ``` statamic new kung-fury-dev the-hoff/kung-fury-theme --local ``` ## Maintaining a Starter Kit When making changes to your Starter Kit, just [re-export](#exporting) from your development repo and push your changes from your exported repo. ### Keeping up-to-date with Statamic and Laravel Rather than maintaining your development repo as new Statamic and Laravel versions are released, you can always install your Starter Kit into a fresh Statamic instance by using the `--with-config` install option. ``` shell statamic new kung-fury-dev the-hoff/kung-fury-theme --with-config ``` This will install your Starter Kit into a brand new Statamic project, along with your `starter-kit.yaml` config file for future exports. ## Making starter kits updatable As their name implies, starter kits were originally intended to be a way to "start" a site. Once installed, the user is on their own and can customize as they see fit. The Kit would get installed via Composer, files would get copied to their respective locations, and then the Kit gets removed. However, you may choose to construct your Kit in a way that it can be updated by the end user. To do that, you should instruct Statamic to leave the Kit required as a Composer dependency by adding `updatable: true` in your `starter-kit.yaml` file: ```yaml updatable: true #[tl! ++] export_paths: ... ``` Now that the Kit package stays around after installation, it can be updated like any other Composer package: ```shell composer update ``` This means that you could do things like: - Add a service provider to wire up Laravel or Statamic behavior. - Make the service provider extend AddonServiceProvider to make your Starter Kit _also_ an addon to get behavior for free like autoload tags, modifiers, etc. - Rather than exporting views, CSS, JS, PHP classes, etc. into the project, you can keep them in the package itself. ## Addons vs. Starter Kits Both addons and Starter Kits can be used to extend the Statamic experience, but they have different strengths and use cases: ### Addons - Addons are installed via `composer`, like any PHP package - Addons live within your app's `vendor` folder after they are installed - Addons can be updated over time - Addon licenses are tied to your site :::tip An example use case is a custom fieldtype maintained by a third party vendor. Though you would install and use the addon within your app, you would still rely on the vendor to maintain and update the addon over time. ::: ### Starter Kits - Starter Kits are installed via `statamic new` or `php please starter-kit:install` - Starter Kits install pre-configured files and settings into your site - Starter Kits do not live as updatable packages within your apps (by default) - Starter Kit licenses are not tied to a specific site, and expire after a successful install :::tip An example use case is a frontend theme with sample content. This is the kind of thing you would install into your app once and modify to fit your own style. You would essentially own and maintain the installed files yourself. ::: ## Related reading - [Starter Kit Overview](/starter-kits) - [How to Install a Starter Kit](/starter-kits/installing-a-starter-kit) - [How to Update a Starter Kit](/starter-kits/updating-a-starter-kit) ================================================ FILE: content/collections/pages/css-javascript.md ================================================ --- id: a92ce050-2c17-4b4d-8a69-c099759c1502 blueprint: page title: 'CSS & JavaScript' intro: 'Statamic can load custom stylesheets and Javascript files located in the `public/vendor/` directory, or from external sources.' --- :::tip This guide is intended for apps adding CSS & JavaScript to the Control Panel. If you're building an addon, please see our [Vite Tooling](/addons/vite-tooling) guide instead. ::: ## Setting up Vite {#using-vite} [Vite](https://vite.dev) is the recommended frontend build tool in the Statamic and Laravel ecosystems. To set up Vite for the Control Panel, run the setup command: ```bash php please setup-cp-vite ``` It will install the necessary dependencies, create a `vite-cp.config.js` file, and publish any necessary stubs. You can add any CSS to the `resources/css/cp.css` file, and any JavaScript to the `resources/js/cp.js` file. To start Vite, run `npm run cp:dev` and to build for production, run `npm run cp:build`. ## HMR and Vue Devtools To use Hot Module Reloading (HMR) or the [Vue Devtools](https://devtools.vuejs.org) browser extension, you will need to publish a special "dev build" of Statamic. You can do this via the `vendor:publish` command: ``` php artisan vendor:publish --tag=statamic-cp-dev ``` Alternatively, it can be symlinked: ``` ln -s /path/to/vendor/statamic/cms/resources/dist-dev public/vendor/statamic/cp-dev ``` Statamic will use the dev build as long as `APP_DEBUG=true` in your `.env` and the `public/vendor/statamic/cp-dev` directory exists. You **shouldn't** commit these or use this on production. ## Inertia The Control Panel is powered by [Inertia.js](https://inertiajs.com), which lets Statamic render pages as Vue components while still using Laravel’s server-side routing. Using Inertia for your custom pages is strongly recommended if you want them to match the SPA-like behaviour seen throughout the Control Panel. To expose a Vue page component to Statamic, register it in your `cp.js` file: ```js import Foo from './pages/Foo.vue'; Statamic.booting(() => { Statamic.$inertia.register('app::Foo', Foo); }); ``` Then return that page from your controller: ```php use Inertia\Inertia; return Inertia::render('app::Foo', [ 'message' => 'Hello world!', ]); ``` All data passed to `Inertia::render()` becomes props on the Vue component. For proper SPA behaviour, make sure your page uses Inertia’s `` component to set the document title, and use `` instead of `` so navigation stays instant and avoids a full refresh: ```vue ``` ## Using ` ``` We also provide a `requireElevatedSessionIf` function allowing you to conditionally require elevated sessions, like this: ```php ``` ### User Forms The user form tags ([login][login_form], [register][register_form], [profile][profile_form], and [password][password_form]) also support Precognition — but at the request level only. The tags themselves don't accept a `js="alpine_precognition"` parameter, so you wire it up manually against the form's action URL. Install the Alpine adapter: ```shell npm install laravel-precognition-alpine ``` Register it before Alpine starts: ```js import Alpine from 'alpinejs' import precognition from 'laravel-precognition-alpine' Alpine.plugin(precognition) Alpine.start() ``` Then bind a `$form` to the appropriate endpoint inside the user form tag: ::tabs ::tab antlers ```antlers {{ user:login_form x-data="{ form: $form('post', '/!/auth/login', { email: '', password: '' }) }" @submit.prevent="form.submit().then(() => window.location = '/dashboard')" }} {{ /user:login_form }} ``` ::tab blade ```blade ``` :: The same pattern applies to the other user forms — just swap the action URL and the data fields: | Tag | Endpoint | |---|---| | `{{ user:login_form }}` | `/!/auth/login` | | `{{ user:register_form }}` | `/!/auth/register` | | `{{ user:profile_form }}` | `/!/auth/profile` | | `{{ user:password_form }}` | `/!/auth/password` | If you'd rather submit normally (full page reload) and only use Precognition for live validation, drop the `@submit.prevent` handler. [tags]: /tags/form [submissions]: /tags/form-submissions [login_form]: /tags/user-login_form [register_form]: /tags/user-register_form [profile_form]: /tags/user-profile_form [password_form]: /tags/user-password_form ================================================ FILE: content/collections/pages/fortrabbit.md ================================================ --- id: 94c521e3-bacb-45e3-b385-00bad3cac401 blueprint: page title: Deploying Statamic with fortrabbit intro: fortrabbit is a managed PHP app hosting solution running on AWS. Well known since 2012. parent: c4f17d05-78bd-41bf-8e06-8dd52f6ec154 --- [fortrabbit](https://www.fortrabbit.com) is a full service provider, no account with another hosting provider required. Just sign up at fortrabbit. ## Creating a New App fortrabbit has a 'try before buy' model. Create your first free trial App with the fortrabbit Dashboard. The free trial is limited in time, but you can ask friendly human support to extend the trial time. While creating an App, choose Laravel as the framework for pre-configuration. Note that this will not install software. It is anticipated that you have a local development environment running Statamic ([see here](/installing)) to be deployed to the fortrabbit platform. ## Choosing a Deployment Method The fortrabbit hosting platform offers integrated Git deployment as well as classical SSH/SFTP access. Depending on your use case and skills pick what fit's you the most. In the following the most popular workflow is shown. Replace variables in curly braces with your settings as provided with the fortrabbit Dashboard: ## Deploying Statamic with Git + rsync * Content changes are synced up and down via rsync * Template and theme code is deployed via Git * Composer dependencies are automatically installed during Git deployment ### Configuring Exclude contents from Git in your `.gitignore` file: ```.gitignore # Exclude stuff you are creating from Git in .gitignore /content /users /resources/blueprints /resources/fieldsets /resources/forms /resources/users /storage/forms /public/assets ``` ### Deploying Code with Git In your local terminal, with the root folder of your Statamic project execute: ```shell # 1. Initialize Git git init # 2. Add your Apps Git remote to your local repo git remote add fortrabbit {{appname}}@deploy.{{region}}.frbit.com:{{appname}}.git # 4. Add changes to Git git add -A # 5. Commit changes git commit -m 'My first commit' # 6. Initial push and upstream git push -u fortrabbit main # From there on only git push ``` ### Deploying Content with rsync Again, in your local terminal, with the root folder of your Statamic project execute: ```shell # SYNC UP: from local to remote $ rsync -avR ./content ./users ./resources/blueprints ./resources/fieldsets ./resources/forms ./resources/users ./storage/forms ./public/build ./storage/app ./public/assets {{appname}}@deploy.{{region}}.frbit.com:~/ ``` It also works down and for specific folders only as shown here: ```shell # SYNC DOWN: from remote to local one by one examples rsync -av {{appname}}@deploy.{{region}}.frbit.com:~/content ./ rsync -av {{appname}}@deploy.{{region}}.frbit.com:~/users ./ … ``` ## Advanced fortrabbit also offers MySQL resources, so you can run Statamic in database mode. The fortrabbit team really cares about Statamic. See [their extensive Statamic guides section](https://help.fortrabbit.com/#statamic) with multiple deployment articles or contact human support if you are hanging somewhere. ================================================ FILE: content/collections/pages/from-wordpress-to-statamic.md ================================================ --- id: 550e7bf1-de6e-40ba-9b06-b32d9119e436 blueprint: page title: 'Switching From WordPress to Statamic' nav_title: 'WordPress to Statamic' intro: |- Thinking about moving from WordPress to Statamic? You wouldn't be the first. If you’ve been in the WordPress world for a while, you’re pretty familiar with one of its biggest strengths — the massive plugin ecosystem. If you're evaluating Statamic against this catalogue you might think our community's few hundred addons aren't nearly enough. Hopefully this guide will open your eyes to a different approach to building sites. One that doesn't require so many addons and taps into a more flexible way of building bigger features out of smaller ones. And we'll show you Statamic's answers to WordPress plugins you're probably most familiar with. --- ## ACF and Custom Fields Arguably one of the best ways to build modern content-driven WordPress sites — especially those with a proper separation of content and style — is with Advanced Custom Fields (ACF) or Pods Framework. Instead of this custom field approach being an afterthought, Statamic was built from the ground up with this approach with 40 different [fieldtypes](/reference/fieldtypes) that you can organize into [blueprints](/blueprints) and reusable fieldsets.
    The Statamic blueprint configuration screen
    A glimpse at configuring a blueprint.
    Your fields are organized into Blueprints, which support sections and tabs for better organization. You have control over field order and width, validation rules, and can even configure conditions that show and hide fields based on your content, making your authoring experience as streamlined and uncluttered as possible for your content team. If you have groups of fields you want to use in multiple Blueprints, you can create a reusable Fieldset that can be imported into any Blueprint, saving you time duplicating configs. It’s super intuitive to manage through the control panel. It feels like ACF, but it’s baked right into the core CMS. ## Gutenberg and block/page builders If you've been working with a Gutenberg or Page Builder approach, take a look at the [Bard](/fieldtypes/bard) and [Replicator](/replicator) fieldtypes — they allow you to create blocks (we call them "sets") out of any _other_ native fieldtypes, giving you virtually unlimited ways to configure your content. These can be used to create numerous components that can be combined as a "page builder" allowing your content team to create and rearrange pages without ever worrying about what it looks like.
    Bard Fieldtype UI
    The Bard Fieldtype in action.
    ### Block to set examples Here is how you could create some common "blocks" with Bard and Replicator sets using our native fieldtypes. #### Hero - [Assets](/fieldtypes/assets) field for a background image - [Color](/fieldtypes/color) field to control a background or overlay color - [Text](/fieldtypes/text) field to edit the `

    ` text - [Group](/fieldtypes/group) field with a Text and [Link](/fieldtypes/link) field to create a call to action button with a destination URL #### Slideshow A single [Assets](/fieldtypes/assets) field is all you need, as Assets themselves can have their own custom fields for alt text, description, caption, credit, etc. #### Blockquote A [Markdown](/fieldtypes/markdown) or [Bard](/fieldtypes/bard) field to hold the quote, and a [Text](/fieldtypes/text) field for the author ``. #### Newsletter signup An empty set works, or a single [HTML](/fieldtypes/html) field letting you insert a display message in your editor saying "Newsletter shown here", and then on the frontend have it render whatever [partial](/tags/partial) you need for the form. #### Video embed A single [video](/fieldtypes/video) fieldtype to paste in the URL of a YouTube or Vimeo video would be enough, but you could add a [Select](/fieldtypes/select) or [Button Group](fieldtypes/button_group) field with some options to control the size of the embed (inline vs oversized, for example). ::: tip These fields store **structured content**, but don't explicitly give control over your _layout_ because they don't write their own HTML. You always have full control of your markup, which in the end makes for a better long-term experience, allowing you to redesign sites without ever having to clean up or rewrite content again. ::: ## Themes Statamic uses [Starter Kits](/starter-kits) instead of traditional themes. These kits go beyond just styling – they can include plugins, custom code, and entire workflows. You can [get Starter Kits from the Marketplace](https://statamic.com/starter-kits), where there are free and commercially available options. For example, the first-party [Cool Writings](https://statamic.com/starter-kits/statamic/cool-writings) starter kit is an excellent choice to use as the basis for a simple blog. We've even made it easy for you to [create your own starter kit](/starter-kits/creating-a-starter-kit). So once you've migrated your WordPress site, why not submit it to the marketplace? ## SEO Yoast SEO is probably the biggest go-to SEO plugin for the WordPress world. It's massive and does many things. But it's also pretty complicated and often overkill, especially for smaller sites. In Statamic, you don’t really _need_ a big plugin to have good SEO. You can get a lot of mileage out of managing all your metadata using our native fields and templates, and then tap into one of the bigger reporting tools like [Moz Seo](https://moz.com/) or [Ahrefs](https://ahrefs.com) to get valuable insight about your site's content. If you want to take it to the next level, you can check out our first-party addon — [SEO Pro](https://statamic.com/addons/statamic/seo-pro). It includes a number of useful features. It: - Sets up all your meta data fields for you, including Open Graph and Twitter data, images, and cards. - Provides a reporting tool to scan your site and make sure all your pages have meta titles, descriptions, and other important SEO factors - Generates sitemaps automatically - Manages Google and Bing site verifications - Generates a [humans.txt](https://humanstxt.org/) file to show who's _behind_ your websites But SEO Pro isn't the only option in the Statamic Ecosystem. You can explore some of the other popular addons: - [Advanced SEO](https://statamic.com/addons/aerni/advanced-seo) - [Aardvark SEO](https://statamic.com/addons/candour/aardvark-seo) - [SEOtamic](https://statamic.com/addons/cnj/seotamic) - [SEO Checker](https://statamic.com/addons/luckymedia/seochecker) ## E-Commerce While there is no do-it-all-and-then-some solution like WooCommerce in the Statamic world, there are still quite a few options that provide a lot of flexibility depending on your specific needs. [Cargo](https://statamic.com/addons/duncanmcclean/cargo) developed by a core team member, provides essential features like product catalogs, shopping carts, and order management. It can handle digital and physical products, tax calculations, and shipping. The [Shopify addon](https://statamic.com/addons/rad-pack/shopify) helps you integrate with Shopify's powerful platform — controlling the frontend of your site with Statamic and leaving the heavy cart, checkout flow, and product management to Shopify. [Donation Checkout](https://statamic.com/addons/ghijk/donation-checkout) lets you accept Stripe payments of arbitrary amounts via Stripe Checkout. There are integrations for [Lemon Squeezy](https://statamic.com/addons/rias/lemon-squeezy) and [Snipcart](https://statamic.com/addons/aerni/snipcart) as well. Additionally, Statamic benefits from Laravel's extensive ecosystem, which includes tools like [Laravel Cashier](https://laravel.com/docs/13.x/billing) for subscription billing, and integrations with payment processors such as Stripe and Paddle. This flexibility allows developers to create fully custom e-commerce solutions tailored to specific needs. ## Forms In WordPress, forms are usually handled by plugins like Contact Form 7, WooForms, or WPForms. Statamic has a built-in [forms feature](/forms) that enable you to manage form fields, collect submissions, provide reports on them on aggregate, and even display user submitted data on the frontend. And if you need more customization, addons like [Flexible Forms](https://statamic.com/addons/addon-foundry/flexible-forms) or [Livewire Forms](https://statamic.com/addons/aerni/livewire-forms) can level it up further. ## Security 95.5% of the content-managed websites hacked are running WordPress ([source](https://sucuri.net/reports/2023-hacked-website-report/)). Also, last year there were almost 6000 vulnerabilities found in themes and plugins ([source](https://patchstack.com/whitepaper/state-of-wordpress-security-in-2024/)). It is the most targeted CMS on the market, which makes plugins like Wordfence, Patchstack, or WPScan critical to your security solution. Here are a few reasons Statamic is more secure than WordPress: - Around 5% of website hacks are done through SQL Injection. Out of the box, Statamic doesn't use a database, thus eliminating most forms of automated attacks. - Statamic's developer team maintains all of the fundamental features most websites need. You will not need 30 plugins by 30 authors on different update schedules. This is one of the reasons why WordPress is so vulnerable. - Statamic is built on [Laravel](https://laravel.com), widely regarded as the most secure and well-maintained PHP framework today. ## Performance WordPress is notoriously slow out of the box, which is generally alleviated by plugins like WP Rocket and Redis caching. We've considered and optimized for performance in every area of Statamic. Built-in smart caching is often enough for most sites to fly right out of the gate, and for those more complex sites that have more heavy lifting or higher traffic — [static caching](/static-caching), Redis caching, or even [static site generation](https://github.com/statamic/ssg) are all native tools at your disposal. ## Spam protection If you’re used to using Akismet to keep spam out of your forms, you can [continue doing so](https://statamic.com/addons/silentz/akismet). ## Redirection Need to manage redirects? In WordPress, you’d likely use the Redirection plugin. In Statamic, there’s an addon for that — [Redirect](https://statamic.com/addons/rias/redirect). You can redirect legacy urls, manage 301 and 302 redirects right from the control panel without any performance impacts. Super simple. ## Backups In WordPress, you might use UpdraftPlus to handle backups, but in Statamic — as long as you're running on flat files — git becomes your backup, version controlling all your changes to content, templates, and configs along the way. If you’re using Statamic Pro, it can even automate your Git commits and pushes. No more worrying about backups, they’re just an invisible part of your workflow. ## Importing content Statamic has a [native Importer](https://github.com/statamic/importer) with support for WordPress's XML or CSV export formats. It supports importing entries, taxonomy terms, and users, and can handle converting Gutenberg content to Bard sets. It even has hooks you can use to customize the import process at any step of the way. ## Everything else - **Slider Revolution:** Build custom sliders with Statamic’s [Replicator field](/fieldtypes/replicator) and plug it into frontend libraries like [Slick](https://kenwheeler.github.io/slick/) or [Flickity](https://flickity.metafizzy.co/). - **MonsterInsights:** You can drop Google Analytics right into Statamic or use the [Ginsights Analytics](https://statamic.com/addons/vijay-software/ginsights-analytics) addon if you want a more integrated feel. - **WPML:** Statamic’s built-in [multi-site](/multi-site) feature helps you manage different languages. - **Mailchimp for WordPress:** Use the [Mailchimp](https://statamic.com/addons/rad-pack/mailchimp) addon to connect directly with your audience. - **Smush, Imagify:** Statamic has you covered with [Glide](/tags/glide), an image manipulation tag that compresses and optimizes images on the fly. - **Comments:** Check out [Meerkat](https://statamic.com/addons/stillat/meerkat-statamic-3). ## Glossary When migrating from WordPress to Statamic, one of the initial challenges is adapting to new terminology. While both systems share many similar concepts, they often use different names for comparable features. Hopefully this table helps point you in the right direction. | WordPress Term | Statamic Equivalent | Notes | |---------------|-------------------|-------| | Post/Page | Entry | Basic content unit in Statamic | | Custom Post Type | Collection | Groups of similar entries | | Category/Tag | Taxonomy | Both systems use taxonomies for classification | | Template | Template/View | Antlers or Blade templates in Statamic | | Theme | Starter Kit | Starter Kits are a starting place, but are not generally interchangeable | | Plugin | Addon | Extends core functionality | | Meta Fields | Fields/Blueprints | Statamic uses YAML for field definitions | | Featured Image | Asset | Part of Statamic's Asset system | | Menu | Navigation | We call menu structures "Navigations" | | Custom Fields/ACF | Fieldtypes | Various content input types and controls | | Gutenberg Block | Bard/Replicator | Rich content editing tools | | Shortcode | Tag | Template tags for dynamic content | | Media Library | Assets | Asset management system | | User Role | User Role/Group | Similar permission systems | | Options/Settings | Globals | Site-wide variables and settings | | Post Status | Status | Published, Draft, etc. | | Author | User | Content creators | | wp-config.php | `.env` or `config/statamic/` | Site configuration | | functions.php | ServiceProvider | For adding functionality | | hooks/filters | Events/Listeners | For modifying core behavior | | Child Theme | - | Statamic uses bespoke themes | | Featured Image | Asset | Hero/main images | ================================================ FILE: content/collections/pages/frontend.md ================================================ --- id: 7e0dd8f1-9988-4173-8453-3ccee12ff976 title: Frontend blueprint: link redirect: url: '@child' status: 301 --- ================================================ FILE: content/collections/pages/getting-started.md ================================================ --- id: 9157e598-81f9-4669-b25a-d9356d8b1c78 title: 'Getting Started' blueprint: link redirect: url: '@child' status: 301 --- ================================================ FILE: content/collections/pages/git-automation.md ================================================ --- id: c095fb87-4c02-462c-9e6f-dfe0b6889248 blueprint: page title: 'Git Automation' intro: "Statamic can automate your version control workflow with Git. It can automatically commit and push content as it's changed, schedule commits, or allow users to commit and push changes from the control panel without having to understand how git works." pro: true updated_by: 3a60f79d-8381-4def-a970-5df62f0f5d56 updated_at: 1632512218 related_entries: - b46adc3b-c4de-4148-a388-c8ff498ae9c9 --- ## Overview Enabling Statamic's Git integration is like having Spock in your enterprise, listening for content changes with those large handsome ears. You won't find anyone more committed. 🖖
    Git utility allowing user to manually trigger commits from control panel Git utility allowing user to manually trigger commits from control panel
    ## Enabling To enable in a specific environment, add the following to your `.env` file: ```env STATAMIC_GIT_ENABLED=true ``` By default, content will be committed automatically as it's changed, but you can customize your git workflow using the provided [configuration options](#configuration). ## Configuration Git workflow can be configured in your `config/statamic/git.php` file, or per environment in your `.env` file. ## Git user By default, Statamic will attempt to use the authenticated user's name and email when committing changes. If you prefer to always use hardcoded git user info, you can disable this by setting `use_authenticated` to `false` in your [configuration](#configuration): ```php 'use_authenticated' => true, 'user' => [ 'name' => env('STATAMIC_GIT_USER_NAME', 'Spock'), 'email' => env('STATAMIC_GIT_USER_EMAIL', 'spock@example.com'), ], ``` _Note: Depending on how you configure your commit workflow, an authenticated user may not always be available. In these cases, Statamic will fall back to the above configured user._ ## Tracked paths You are free to define the tracked paths to be considered when staging and committing changes. Default stache and file locations are already set up for you, but feel free to modify these paths in your [configuration](#configuration) to suit your storage config. ```php 'paths' => [ base_path('content'), base_path('users'), resource_path('addons'), resource_path('blueprints'), resource_path('fieldsets'), resource_path('forms'), resource_path('users'), resource_path('preferences.yaml'), resource_path('sites.yaml'), storage_path('forms'), public_path('assets'), ], ``` :::tip You may also reference absolute paths to external repositories! If Statamic detects an external repository path, changes will be staged and committed relative to your external repository. ::: ## Committing changes By default, Statamic listens to various `Saved` and `Deleted` data events to determine when your content is changed, and will automatically commit your changes. If you prefer users to manually trigger commits using the Git utility interface, you may set this to `false` in your [configuration](#configuration): ```php 'automatic' => env('STATAMIC_GIT_AUTOMATIC', false), ``` Or in a specific environment's `.env` file: ```env STATAMIC_GIT_AUTOMATIC=false ``` ### From the command line Manually trigger commits via the command line with the following command: ``` shell php please git:commit ``` ## Pushing changes Statamic can also `git push` your changes after committing. Enable this behavior in your [configuration](#configuration): ```php 'push' => env('STATAMIC_GIT_PUSH', true) ``` Or in a specific environment's `.env` file: ```env STATAMIC_GIT_PUSH=true ``` ### Remote setup When pushing, Statamic assumes you have a [Git remote](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes) with an upstream branch set, and are authenticated to push to your remote [via SSH](https://docs.github.com/en/github/using-git/which-remote-url-should-i-use). :::tip If you use [Laravel Forge](https://forge.laravel.com/) or [Ploi](https://ploi.io/) to deploy your site, a git remote and upstream branch will automatically be configured for you. ::: If a remote and upstream is not already configured for your deployment, you may need to set this up manually on your server. For example, the following commands could be run from your deployment folder to add an `origin` remote and track the `master` branch: ```shell git init git remote add origin git@github.com:your/remote-repository.git git fetch git branch --track master origin/master git reset HEAD ``` ## Queueing commits When automatic [committing](#committing-changes) is enabled, commits are automatically pushed onto a [queue](https://laravel.com/docs/queues) for processing. By default, your Statamic app is configured to use the `sync` queue driver, which will run the job immediately after your content is saved during the web request. You have the option to set a dedicated queue connection using the `STATAMIC_GIT_QUEUE_CONNECTION` environment variable. ```env STATAMIC_GIT_QUEUE_CONNECTION=redis ``` ### Queueing for performance If you are experiencing slow-down when saving or deleting content in the control panel, we recommended configuring another queue driver so that commits can be run by a background process, which will help keep the experience fast for your users. :::tip A popular choice is to use a [Redis](https://laravel.com/docs/redis) store and [queue driver](https://laravel.com/docs/queues#driver-prerequisites), along with [Laravel Horizon](https://laravel.com/docs/horizon) for managing your Redis queues. ::: _Note: When commits are run by a queue's background process, there will be no authenticated user. In this case, Statamic will use the hardcoded git user in your [configuration](#configuration)._ ## Delaying Commits When [queueing commits](#queueing-commits), you can also [configure](#configuration) a dispatch delay for your commits: ```php 'dispatch_delay' => env('STATAMIC_GIT_DISPATCH_DELAY', 10), ``` Or in a specific environment's `.env` file: ```env STATAMIC_GIT_DISPATCH_DELAY=10 ``` In this example, we queue a delayed commit to run 10 minutes after a user makes a content change. If at that time the repository status is clean, the commit will be cancelled. Please note that the default `sync` queue driver does not support this. Use another queue driver like `redis` instead. :::tip Since all tracked paths are committed at once, this can allow for more consolidated commits when you have multiple users making simultaneous content changes to your repository. ::: ## Scheduling commits You can also [schedule](https://laravel.com/docs/scheduling) commits to run via cron job at regular intervals within your `routes/console.php` file: ```php everyTenMinutes(); ``` In this example, we schedule a commit to run 10 minutes after a user makes a content change. If at that time the repository status is clean, the commit will be cancelled. _Note: If you have never used Laravel's scheduler, be sure to also [configure a cron job on your server](https://laravel.com/docs/scheduling#running-the-scheduler) to run all scheduled jobs. This only needs to be done once per server._ ``` * * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1 ``` ### Scheduling for performance Scheduling commits can be a great alternative to [queueing](#queueing-commits), for when you don't have a proper queue setup. If you choose to schedule commits, just be sure to [disable automatic commit functionality](#committing-changes) as well. :::tip Since all tracked paths are committed at once, this can allow for more consolidated commits when you have multiple users making simultaneous content changes to your repository. ::: _Note: When commits are scheduled to run via cron, there will be no authenticated user. In this case, Statamic will use the hardcoded git user in your [configuration](#configuration)._ ## Customizing commits To customize the commit messages themselves, modify the `commands` array in the [configuration](#configuration) file. For example, you can append `[BOT]` to the commit message so that you can selectively disable automatic site deployments when a commit is [automatically pushed](#pushing-changes) back to your repository: ```php 'commands' => [ 'git add {{ paths }}', 'git -c "user.name={{ name }}" -c "user.email={{ email }}" commit -m "{{ message }} [BOT]"', ], ``` In your deploy scripts on [Forge](https://forge.laravel.com), [Ploi](https://ploi.io), or [Cleavr](https://cleavr.io) you could then add the following: ### Forge ``` shell if [[ $FORGE_DEPLOY_MESSAGE =~ "[BOT]" ]]; then echo "AUTO-COMMITTED ON PRODUCTION. NOTHING TO DEPLOY." exit 0 fi ``` ### Ploi ``` shell if [[ {COMMIT_MESSAGE} =~ "[BOT]" ]]; then echo "AUTO-COMMITTED ON PRODUCTION. NOTHING TO DEPLOY." exit 0 fi ``` ### Cleavr ``` shell if [[ "{{ commitMessage }}" =~ "[BOT]" ]]; then echo 'AUTO-COMMITTED ON PRODUCTION. NOTHING TO DEPLOY.' exit 1 fi ``` ## Ignoring Events When [automatically committing](#committing-changes), Statamic will listen on all `Saved` and `Deleted` events, as well as any events registered by installed addons. To ignore specific events, add them to the `ignored_events` array in the [configuration](#configuration) file. For example, if you're [storing users in a database](/tips/storing-users-in-a-database), you may wish to ignore user-based events: ```php 'ignored_events' => [ \Statamic\Events\Data\UserSaved::class, \Statamic\Events\Data\UserDeleted::class, ], ``` :::tip When ignoring events, you may also wish to remove any related [tracked paths](#tracked-paths) from your configuration. ::: ## Addon events When building an addon that provides its own content saved events, you should register those events with our git listener in your addon service provider: ```php \Statamic\Facades\Git::listen(PunSaved::class); ``` This will allow your end users to track addon-related content in their [tracked paths](#tracked-paths), if they decide to opt-in for automatic commit and push when saving. ### Providing a default commit message You can also provide a default commit message by implementing the `ProvidesCommitMessage` interface with a `commitMessage()` method definition: ```php item = $item; } public function commitMessage() { return __('Pun saved'); } } ``` ================================================ FILE: content/collections/pages/globals.md ================================================ --- title: Globals intro: Global variables store content that belongs to the **whole site**, not just a single page or URL. Globals are available everywhere, in all of your views, all of the time. Just like the memory of eating your first hot pepper. 🌶 template: page id: 1e91dd54-c452-4e3b-8972-dba83c048d3d blueprint: page --- ## Overview Globals are intended to be used for **reusable content** or content that **belongs to the site** and not just one page. - Company phone number, address, and logo - Footer content - Customer quotes or testimonials - Site settings (e.g. on/off toggles for various features) - Success and error message text ## Global sets Globals are organized into "sets", each containing [fields](/fields). This convention helps you keep groups of globals together and stay organized. Each set also acts as a "scope" for templating purposes.
    Statamic Global Set Example Statamic Global Set Example
    Global Set
    ## Storage Globals are stored in the `content/globals` directory. ``` files theme:serendipity-light content/ globals/ global.yaml footer.yaml default/ global.yaml footer.yaml ``` The `content/globals/{handle}.yaml` file contains metadata about the global set, like its title. While, the actual data for the global set is stored in `content/globals/{site}/{handle}.yaml`. ``` yaml # content/globals/footer.yaml title: Footer ``` ```yaml # content/globals/default/footer.yaml copyright: 2021 Neat Fake Company, LLC flair: Made with ❤️ by humans ``` ## Frontend templating ::tabs ::tab antlers In this example all of the variables inside a `footer` global set will be accessed through `footer:`. ```antlers

    {{ footer:copyright }}

    {{ footer:flair }}

    ``` ::tab blade In this example all of the variables inside a `footer` global set will be accessed through `$footer->{$var_name}`. ```blade

    {{ $footer->copyright }}

    {{ $footer->flair }}

    ``` :: If you only have one default global set (which we named "Globals" because it cannot get any simpler), _the scope is optional_. You can access them with either `{{ var_name }}` or `{{ global:var_name }}`. ## Blueprints are optional If you don't explicitly create a [Blueprint](/blueprints) for your global set, Statamic will treat each key in the YAML file as a text variable. Blueprints only become necessary when you need more control over which fieldtype you want used, wish to create fields before you have the content to put in them, or want to work with [GraphQL](/graphql) If you _do_ want a blueprint, you can configure it in the Control Panel's Global Settings. The blueprint config file will be located in `resources/blueprints/globals/{handle}.yaml`. Unrelated, "Lorem Ipsum" is an adorable name for a little girl. ## Localization When running a [multi-site](/multi-site) installation, you can have globals existing in multiple sites with different content. [Read about localizing globals](/tips/localizing-globals) ## Ideas on how to use globals Here are a few more ideas what you can use globals for: - **Theme or design settings**, with [assets fields](/fieldtypes/assets) for logo, and favicon and [colors fields](/fieldtypes/color) to set brand colors. - **JavaScript embed codes**, using a [replicator field](/fieldtypes/replicator) to add any number of [textarea fields](/fieldtypes/textarea) for analytics, pixel trackers, and other "copy and paste this before the `` tag" type things - **Interactive text-adventure games**. Not really sure how you'd do it honestly, but we'd like to see someone try. ================================================ FILE: content/collections/pages/graphql.md ================================================ --- title: 'GraphQL API' intro: 'The GraphQL API is a **read-only** API for delivering content from Statamic to your frontend, external apps, SPAs, and numerous other possible sources. Content is delivered as JSON data.' pro: true blueprint: page id: fc564ddf-80c1-4d87-8675-4a41f13c7774 --- (If you're interested in a [REST API](/content-api), we have one of those too.) ## Enable GraphQL To enable the GraphQL API, add the following to your `.env` file: ```env STATAMIC_GRAPHQL_ENABLED=true ``` Or you can enable it for all environments in `config/statamic/graphql.php`: ```php 'enabled' => true, ``` You will also need to [enable the resources](#enable-resources) you want to be available. For security, they're all disabled by default. :::tip When GraphQL is enabled, [GraphiQL](https://github.com/graphql/graphiql) is available in the Control Panel. This allows you to explore and test available queries and fields. ::: :::tip Heads up If you publish the underlying [package's](#laravel-package) config, the query routes will be enabled regardless of whether you've disabled it in the Statamic config. ::: ### Enable resources You can enable resources (ie. Collections, Taxonomies, etc.) in your `config/statamic/graphql.php` config: ```php 'resources' => [ 'collections' => true, 'taxonomies' => true, // etc. ] ``` ### Enable specific sub-resources If you want more granular control over which sub-resources are enabled within a resource type (ie. enabling specific Collection queries only), you can use array syntax: ```php 'resources' => [ 'collections' => [ 'articles' => true, 'pages' => true, // 'events' => false, // Sub-resources are disabled by default ], 'taxonomies' => true, // etc. ] ``` ## Interfaces Statamic will provide "interface" types, which describe more generic items. For instance, an `EntryInterface` exists for all entries, which would provide fields like `id`, `slug`, `status`, `title`, and so on. In addition to the interfaces, Statamic will provide implementations of them, which would come from the blueprints. For example, if you had a collection named `pages`, and it had blueprints of `page` and `home`, you would find `Entry_Pages_Page` and `Entry_Pages_Home` types. These implementations would provide fields specific to the blueprint, like `subtitle`, `content`, etc. ```graphql { entries { id title data { ... on Entry_Pages_Page { subtitle content } ... on Entry_Pages_Home { hero_intro hero_image } } } } ``` ## Queries Statamic has a number of root level queries you can perform to get data. You can read about the [available queries](#available-queries) further down the page, but know that you can perform more than one query at a time. They just need to be at the top level of your GraphQL query body. For example, the following would perform both `entries` and `collections` queries ```graphql { entries { # ... } collections { # ... } } ``` The response will contain the results of both queries: ```json { "entries": { /* ... */ }, "collections": { /* ... */ }, } ``` Note that you can even perform the same query multiple times. If you want to do this, you should use aliases: ```graphql { home: entry(id: "home") { title } contact: entry(id: "contact") { title } } ``` ```json { "home": { /* ... */ }, "contact": { /* ... */ }, } ``` ## Available queries - [Ping](#ping-query) - [Collections](#collections-query) - [Collection](#collection-query) - [Entries](#entries-query) - [Entry](#entry-query) - [Asset Containers](#asset-containers-query) - [Asset Container](#asset-container-query) - [Assets](#assets-query) - [Asset](#asset-query) - [Taxonomies](#taxonomies-query) - [Taxonomy](#taxonomy-query) - [Terms](#terms-query) - [Term](#term-query) - [Global Sets](#global-sets-query) - [Global Set](#global-set-query) - [Navs](#navs-query) - [Nav](#nav-query) ### Ping {#ping-query} Used for testing that your connection works. If you send a query of `{ping}`, you should receive `{"data": {"ping": "pong"}}`. ```graphql { ping } ``` ```json { "data": { "ping": "pong" } } ``` ### Collections {#collections-query} Used for querying collections. Returns a list of [Collection](#collection-type) types. ```graphql { collections { handle title } } ``` ```json { "collections": [ { "handle": "blog", "title": "Blog Posts" }, { "handle": "events", "title": "Events" }, ] } ``` ### Collection {#collection-query} Used for querying a single collection. Returns a [Collection](#collection-type) type. ```graphql { collection(handle: "blog") { handle title } } ``` ```json { "collections": { "handle": "blog", "title": "Blog Posts" } } ``` ### Entries {#entries-query} Used for querying multiple entries. Returns a [paginated](#pagination) list of [EntryInterface](#entry-interface) types. | Argument | Type | Description | |----------|------|-------------| | `collection` | `[String]` | Narrows down the results by entries in one or more collections. | `limit` | `Int` | The number of results to be shown per paginated page. | `page` | `Int` | The paginated page to be shown. Defaults to `1`. | `filter` | `JsonArgument` | Narrows down the results based on [filters](#filtering). | `sort` | `[String]` | [Sorts](#sorting) the results based on one or more fields and directions. Example query and response: ```graphql { entries { current_page data { id title } } } ``` ```json { "entries": { "current_page": 1, "data": [ { "id": 1, "title": "First Entry" }, { "id": 2, "title": "Second Entry" } ] } } ``` ### Entry {#entry-query} Used for querying a single entry. ```graphql { entry(id: 1) { id title } } ``` ```json { "entry": { "id": 1, "title": "First Entry" } } ``` ### Asset containers {#asset-containers-query} Used for querying asset containers. ```graphql { assetContainers { handle title } } ``` ```json { "assetContainers": [ { "handle": "images", "title": "Images" }, { "handle": "documents", "title": "Documents" }, ] } ``` ### Asset container {#asset-container-query} Used for querying a single asset container. Returns an [AssetContainer](#asset-container-type) type. ```graphql { assetContainer(handle: "images") { handle title } } ``` ```json { "assetContainer": { "handle": "images", "title": "Images" } } ``` | Argument | Type | Description | |----------|------|-------------| | `handle` | `String!` | Specifies which asset container to retrieve. ### Assets {#assets-query} Used for querying multiple assets of an asset container. Returns a [paginated](#pagination) list of [AssetInterface](#asset-interface) types. | Argument | Type | Description | |----------|------|-------------| | `container` | `String!` | Specifies which asset container to query. | `limit` | `Int` | The number of results to be shown per paginated page. | `page` | `Int` | The paginated page to be shown. Defaults to `1`. | `filter` | `JsonArgument` | Narrows down the results based on [filters](#filtering). | `sort` | `[String]` | [Sorts](#sorting) the results based on one or more fields and directions. Example query and response: ```graphql { assets(container: "images") { current_page data { url } } } ``` ```json { "entries": { "current_page": 1, "data": [ { "url": "/assets/images/001.jpg" }, { "url": "/assets/images/002.jpg" }, ] } } ``` ### Asset {#asset-query} Used for querying a single asset. ```graphql { asset(id: 1) { id title } } ``` ```json { "asset": { "id": 1, "title": "First Entry" } } ``` You can either query by `id`, or by `container` and `path` together. | Argument | Type | Description | |----------|------|-------------| | `id` | `String` | The ID of the asset. If you use this, you don't need `container` or `path`. | `container` | `String` | The container to look for the asset. You must also provide the `path`. | `path` | `String` | The path to the asset, relative to the container. You must also provide the `container`. ### Taxonomies {#taxonomies-query} Used for querying taxonomies. ```graphql { taxonomies { handle title } } ``` ```json { "taxonomies": [ { "handle": "tags", "title": "Tags" }, { "handle": "categories", "title": "Categories" }, ] } ``` ### Taxonomy {#taxonomy-query} Used for querying a single taxonomy. ```graphql { taxonomy(handle: "tags") { handle title } } ``` ```json { "taxonomy": { "handle": "tags", "title": "Tags" } } ``` ### Terms {#terms-query} Used for querying multiple taxonomy terms. Returns a [paginated](#pagination) list of [TermInterface](#term-interface) types. | Argument | Type | Description | |----------|------|-------------| | `taxonomy` | `[String]` | Narrows down the results by terms in one or more taxonomies. | `limit` | `Int` | The number of results to be shown per paginated page. | `page` | `Int` | The paginated page to be shown. Defaults to `1`. | `filter` | `JsonArgument` | Narrows down the results based on [filters](#filtering). | `sort` | `[String]` | [Sorts](#sorting) the results based on one or more fields and directions. Example query and response: ```graphql { terms { current_page data { id title } } } ``` ```json { "terms": { "current_page": 1, "data": [ { "id": "tags::one", "title": "Tag One" }, { "id": "tags::two", "title": "Tag Two" } ] } } ``` ### Term {#term-query} Used for querying a single taxonomy term. ```graphql { term(id: "tags::one") { id title } } ``` ```json { "term": { "id": "tags::one", "title": "Tag One" } } ``` ### Global sets {#global-sets-query} Used for querying multiple global sets. Returns a list of [GlobalSetInterface](#global-set-interface) types. | Argument | Type | Description | |----------|------|-------------| | `taxonomy` | `[String]` | Narrows down the results by terms in one or more taxonomies. | `limit` | `Int` | The number of results to be shown per paginated page. | `page` | `Int` | The paginated page to be shown. Defaults to `1`. | `sort` | `[String]` | [Sorts](#sorting) the results based on one or more fields and directions. Example query and response: ```graphql { globalSets { title handle ... on GlobalSet_Social { twitter } ... on GlobalSet_Company { company_name } } } ``` ```json { "globalSets": [ { "handle": "social", "twitter": "@statamic" }, { "handle": "company", "company_name": "Statamic" }, ] } ``` ### Global set {#global-set-query} Used for querying a single global set. ```graphql { globalSet(handle: "social") { title handle ... on GlobalSet_Social { twitter } } } ``` ```json { "globalSet": { "title": "Social", "handle": "social", "twitter": "@statamic", } } ``` ### Forms {#forms-query} Used for querying multiple forms. ```graphql { forms { handle title fields { handle display } } } ``` ```json { "forms": [ { "handle": "contact", "title": "Contact", "fields": [ { "handle": "name", "display": "Name" }, { "handle": "email", "display": "Email" }, { "handle": "inquiry", "display": "Inquiry" } ] } ] } ``` ### Form {#form-query} Used for querying a single form. ```graphql { form(handle: "contact") { handle title fields { handle display } } } ``` ```json { "form": { "handle": "contact", "title": "Contact", "fields": [ { "handle": "name", "display": "Name" }, { "handle": "email", "display": "Email" }, { "handle": "inquiry", "display": "Inquiry" } ] } } ``` ### Navs {#navs-query} Used for querying Navs. ```graphql { navs { handle title } } ``` ```json { "navs": [ { "handle": "header_links", "title": "Header Links" }, { "handle": "footer_links", "title": "Footer Links" }, ] } ``` ### Nav {#nav-query} Used for querying a single Nav. ```graphql { nav(handle: "footer") { handle title } } ``` ```json { "nav": { "handle": "footer", "title": "Footer Links" } } ``` ### Users {#users-query} Used for querying multiple users. | Argument | Type | Description | |----------|------|-------------| | `limit` | `Int` | The number of results to be shown per paginated page. | `page` | `Int` | The paginated page to be shown. Defaults to `1`. | `filter` | `JsonArgument` | Narrows down the results based on [filters](#filtering). | `sort` | `[String]` | [Sorts](#sorting) the results based on one or more fields and directions. Example query and response: ```graphql { users { current_page data { name email } } } ``` ```json { "users": { "current_page": 1, "data": [ { "name": "David Hasselhoff", "email": "thehoff@statamic.com" }, { "name": "Chuck Norris", "email": "norris@statamic.com" }, ] } } ``` ### User {#user-query} Used for querying a single user. ```graphql { user(email: "thehoff@statamic.com") { name email } } ``` ```json { "user": { "name": "David Hasselhoff", "email": "thehoff@statamic.com" } } ``` You can query by either `id` or `email`. | Argument | Type | Description | |----------|------|-------------| | `id` | `String` | The ID of the user. If you use this, you don't `email`. | `email` | `String` | The email address of the user. If you use this, you don't `id`. ## Custom queries Here's an example of a basic query class. It has the name attribute which is the key the user needs to put in the request, any number of middleware, the type(s) that will be returned, any arguments, and how the data should be resolved. ```php use Statamic\Facades\GraphQL; use Statamic\GraphQL\Queries\Query; class Products extends Query { protected $attributes = [ 'name' => 'products', ]; protected $middleware = [ MyMiddleware::class, ]; public function type(): Type { return GraphQL::paginate(GraphQL::type(ProductType::NAME)); } public function args(): array { return [ 'limit' => GraphQL::int(), ]; } public function resolve($root, $args) { return Product::paginate($args['limit']); } } ``` ```graphql { products { name price } } ``` You may add your own queries to Statamic's default schema. You can add them to the config file, which makes sense for app specific queries: ```php // config/statamic/graphql.php 'queries' => [ MyCustomQuery::class ] ``` Or, you may use the `addQuery` method on the facade, which would be useful for addons. ```php GraphQL::addQuery(MyCustomQuery::class); ``` ## Types - [EntryInterface](#entry-interface) - [Collection](#collection-type) - [CollectionStructure](#collection-structure-type) - [CollectionTreeBranch](#collection-tree-branch-type) - [NavTreeBranch](#nav-tree-branch-type) - [PageInterface](#page-interface) - [TermInterface](#term-interface) - [AssetInterface](#asset-interface) - [GlobalSetInterface](#global-set-interface) - [Code](#code-type) ### EntryInterface {#entry-interface} | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | | `title` | `String!` | Each `EntryInterface` will also have implementations for each collection/blueprint combination. You will need to query the implementations using fragments in order to get blueprint-specific fields. ```graphql { entries { id title data { ... on Entry_Blog_Post { intro content } ... on Entry_Blog_ArtDirected_Post { hero_image content } } } } ``` The fieldtypes will define their types. For instance, a text field will be a `String`, a [grid](#grid-fieldtype) field will expose a list of `GridItem` types. ### Collection {#collection-type} | Field | Type | Description | |-------|------|-------------| | `handle` | `String!` | | `title` | `String!` | | `structure` | [`CollectionStructure`](#collection-structure-type) | If the collection is structured (e.g. a "pages" collection), you can use this to query its tree. ### CollectionStructure {#collection-structure-type} | Field | Type | Description | |-------|------|-------------| | `handle` | `String!` | | `title` | `String!` | | `tree` | [[`CollectionTreeBranch`](#collection-tree-branch-type)] | A list of tree branches. ### CollectionTreeBranch {#collection-tree-branch-type} Represents a branch within a structured collection's tree. | Field | Type | Description | |-------|------|-------------| | `depth` | `Int!` | The nesting level of the current branch. | `entry` (or `page`) | [`EntryInterface`](#entry-interface) | Contains the entry's fields. | `children` | [[`CollectionTreeBranch`](#collection-tree-branch-type)] | A list of tree branches. :::tip It's not possible to perform recursive queries in GraphQL. If you want to retrieve multiple levels of child branches, take a look at a workaround in [recursive tree branches](#recursive-tree-branches) below. ::: ### NavTreeBranch {#nav-tree-branch-type} Represents a branch within a nav's tree. | Field | Type | Description | |-------|------|-------------| | `depth` | `Int!` | The nesting level of the current branch. | `page` | [`PageInterface`](#page-interface) | Contains the page's fields. | `children` | [[`NavTreeBranch`](#nav-tree-branch-type)] | A list of tree branches. :::tip It's not possible to perform recursive queries in GraphQL. If you want to retrieve multiple levels of child branches, take a look at a workaround in [recursive tree branches](#recursive-tree-branches) below. ::: ### PageInterface {#page-interface} A "page" within a nav's tree. | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | The ID of the page. | `entry_id` | `ID` | The `entry` ID. | `title` | `String` | For entry pages, it's the entry's `title` unless overridden on the branch. For basic pages, it's the `title`. | `url` | `String` | For entry pages, it's the entry's `url`. For basic pages, it's the `url`. For text-only pages it'll be null. | `permalink` | `String` | The absolute version of `url`. If you want to query any fields that you've added to the nav's blueprint, you have 4 different options available to you that you can use as inline fragments. You can use more than one at a time: - `EntryInterface` for all entry pages. - `NavEntryPage_{NavHandle}_{Collection}_{Blueprint}` for a specific entry/blueprint combination on entry pages. - `NavBasicPage_{NavHandle}` for basic non-entry pages. - `NavPage_{NavHandle}` for either basic or entry pages. ```graphql page { title url ... on EntryInterface { # ... } ... on NavPage_HeaderLinks { # ... } ... on NavBasicPage_HeaderLinks { # ... } ... on NavEntryPage_HeaderLinks_Blog_ArtDirected { # ... } } ``` ### TermInterface {#term-interface} | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | | `title` | `String!` | | `slug` | `String!` | Each `TermInterface` will also have implementations for each taxonomy/blueprint combination. You will need to query the implementations using fragments in order to get blueprint-specific fields. ```graphql { terms { id title ... on Term_Tags_RegularTag { content } ... on Term_Tags_SpecialTag { how_special content } } } ``` The fieldtypes will define their types. For instance, a text field will be a `String`, a [grid](#grid-fieldtype) field will expose a list of `GridItem` types. ### AssetInterface {#asset-interface} | Field | Type | Description | |-------|------|-------------| | `path` | `String!` | The path to the asset. Each `AssetInterface` will also have an implementation for each asset container's blueprint. You will need to query the implementations using fragments in order to get blueprint-specific fields. ```graphql { entries { path ... on Asset_Images { alt } } } ``` The fieldtypes will define their types. For instance, a text field will be a `String`, a [grid](#grid-fieldtype) field will expose a list of `GridItem` types. ### GlobalSetInterface {#global-set-interface} | Field | Type | Description | |-------|------|-------------| | `handle` | `String!` | The handle of the set. | `title` | `String!` | The title of the set. Each `GlobalSetInterface` will also have an implementation for each set's blueprint. :::tip While Statamic doesn't enforce a blueprint for globals (see [Blueprint is Optional](/globals#blueprints-are-optional)), it _is_ required within the GraphQL context. Fields that haven't been explicitly added to a blueprint will not be available. ::: You will need to query the implementations using fragments in order to get blueprint-specific fields. ```graphql { globalSets { handle ... on GlobalSet_Social { twitter } } } ``` The fieldtypes will define their types. For instance, a text field will be a `String`, a [grid](#grid-fieldtype) field will expose a list of `GridItem` types. ### Code {#code-type} | Field | Type | Description | |-------|------|-------------| | `code` | `String!` | The actual code value. | `mode` | `String!` | The language "mode". The [code fieldtype](/fieldtypes/code) will return this type when `mode_selectable` is enabled. Otherwise, it'll just be a string. ```graphql { snippet { code mode } } ``` ## Filtering ### Enabling filters For security, [filtering](#filtering) is disabled by default. To enable, you'll need to opt in by defining a list of `allowed_filters` for each sub-resource in your `config/statamic/graphql.php` config: ```php 'resources' => [ 'collections' => [ 'articles' => [ 'allowed_filters' => ['title', 'status'], ], 'pages' => [ 'allowed_filters' => ['title'], ], 'events' => true, // Enable this collection without filters 'products' => true, // Enable this collection without filters ], 'taxonomies' => [ 'topics' => [ 'allowed_filters' => ['slug'], ], 'tags' => true, // Enable this taxonomy without filters ], // etc. ], ``` For queries that don't have sub-resources (ie. users), you can define `allowed_filters` at the top level of that resource config: ```php 'resources' => [ 'users' => [ 'allowed_filters' => ['name', 'email'], ], ], ``` ### Using filters You can filter the results of listing queries (like `entries`) using the `filter` argument. This argument accepts a JSON object containing different [conditions](/conditions). ```graphql { entries(filter: { title: { contains: "rad", ends_with: "!" } }) { data { title } } } ``` ```json { "data": [ { "title": "That was so rad!" }, { "title": "I wish I was as cool as Daniel Radcliffe!" }, ] } ``` If you only need to do a simple "equals" condition, then you can use a string and omit the condition name, like the `rating` here: ```graphql { entries(filter: { title: { contains: "rad" } rating: 5 }) { # ... } ``` If you need to use the same condition on the same field more than once, you can use the array syntax: ```graphql { entries(filter: { title: [ { contains: "rad" }, { contains: "awesome" }, ] }) { # ... } ``` ### Advanced filtering config You can also allow filters on all enabled sub-resources using a `*` wildcard config. For example, here we'll enable only the `articles`, `pages`, and `products` collections, with `title` filtering enabled on each, in addition to `status` filtering on the `articles` collection specifically: ```php 'resources' => [ 'collections' => [ '*' => [ 'allowed_filters' => ['title'], // Enabled for all collections ], 'articles' => [ 'allowed_filters' => ['status'], // Also enable on articles ], 'pages' => true, 'products' => true, ], ], ``` If you've enabled filters using the `*` wildcard config, you can disable filters on a specific sub-resource by setting `allowed_filters` to `false`: ```php 'resources' => [ 'collections' => [ '*' => [ 'allowed_filters' => ['title'], // Enabled for all collections ], 'articles' => [ 'allowed_filters' => false, // Disable filters on articles ], 'pages' => true, 'products' => true, ], ], ``` Or you can enable queries and filters on all sub-resources at once by setting both `enabled` and `allowed_filters` within your `*` wildcard config: ```php 'resources' => [ 'collections' => [ '*' => [ 'enabled' => true, // All collection queries enabled 'allowed_filters' => ['title'], // With filters enabled for all ], ], ], ``` ## Sorting You can sort the results of listing queries (like `entries`) on one or multiple fields, in any direction. ```graphql { entries(sort: "title") { # ... } ``` ```graphql { entries(sort: "title desc") { # ... } ``` ```graphql { entries(sort: ["price desc", "title asc"]) { # ... } ``` ## Pagination Some queries (like [entries](#entries-query)) will provide their results using pagination. In a paginated response, you will find the actual items within a `data` key. By default there will be `1000` per page. You can change this using a `limit` argument. You can specify the current paginated page using the `page` argument. ```graphql { entries(limit: 15, page: 2) { current_page has_more_pages data { # ... } } } ``` | Field | Type | Description | |-------|------|-------------| | `data` | [mixed] | A list of items on the current page. In an `entries` query, there will be `EntryInterface` types, etc. | `total` | `Int!` | Number of total items selected by the query. | `per_page` | `Int!` | Number of items returned per page. | `current_page` | `Int!` | Current page of the cursor. | `from` | `Int` | Number of the first item returned. | `to` | `Int` | Number of the last item returned. | `last_page` | `Int!` | The last page (number of pages). | `has_more_pages` | `Boolean!` | Determines if cursor has more pages after the current page. ## Fieldtypes ### Replicator Replicator fields require that you query each set using a separate fragment. The fragments are named after your configured sets using StudlyCased field and set handles. e.g. `Set_{ReplicatorFieldName}_{SetHandle}` ```yaml fields: - handle: content_blocks field: type: replicator sets: image: fields: - handle: image type: assets max_files: 1 pull_quote: fields: - handle: quote field: type: textarea - handle: author field: type: text ``` ```graphql { content_blocks { ... on Set_ContentBlocks_Image { type image } ... on Set_ContentBlocks_PullQuote { type quote author } } } ``` :::tip If you have nested fields, include each parent's handle, (and grandparent's, great grandparent's etc), like so: `Set_TopLevelReplicator_NestedReplicator_DeeplyNestedReplicator_SetHandle` ::: ### Bard Bard fields work the same as Replicator, except that you also have an additional `BardText` for the text fragment. ```graphql { content_blocks { ... on BardText { type text } ... on Set_ContentBlocks_Image { type image } ... on Set_ContentBlocks_PullQuote { type quote author } } } ``` ### Grid Grid fields can be queried with no extra requirements. You can just use the nested field handles. ```graphql { cars { make model } } ``` ### Select, radio, checkboxes, and button group These fieldtypes provide you with labels and values. You'll need to use a sub selection. ```graphql my_select_field { value label } ``` ```json "my_single_select_field": { "value": "potato", "label": "Potato" } ``` The same syntax is used when multiple values are expected. e.g. a select field with multiple values enabled, or a checkboxes field. You'll just get a nested array returned. ```json "my_multi_select_field": [ { "value": "potato", "label": "Potato" }, { "value": "tomato", "label": "Tomato", } ] ``` ## Recursive tree branches Often, when dealing with navs, you need to recursively output all the child branches. For example, when using the `nav` tag in Antlers, you might do something like this: ```
    ``` In GraphQL, it's not possible to perform recursive queries like that. You'll need to explicitly query each level: ```graphql { nav(handle: "links") { tree { page { title url } children { page { title url } children { page { title url } } } } } } ``` In this example, if you wanted anything more than `title` and `url`, you'd need to add them to each level. This can quickly become tedious and is very repetitive, so here's a workaround using fragments. If you wanted to add more fields, you only need to do it one spot - the `Fields` fragment. If you want to query more levels, you can just increase the nesting level of the `RecursiveChildren` fragment. ```graphql { nav(handle: "links") { tree { ...Fields ...RecursiveChildren } } } fragment Fields on NavTreeBranch { depth page { title url # any other fields you want for each branch } } fragment RecursiveChildren on NavTreeBranch { children { ...Fields children { ...Fields children { ...Fields # just keep repeating this as deep as necessary } } } } ``` Hat tip to Hash Interactive for their [blog post](https://hashinteractive.com/blog/graphql-recursive-query-with-fragments/) on this technique. ## Custom fieldtypes A fieldtype can define what GraphQL type will be used. By default, all fieldtypes will return strings. ```php use GraphQL\Type\Definition\Type; public function toGqlType() { return GraphQL::string(); } ``` You're free to return an array with a more complicated structure in order to provide arguments, etc. ```php use GraphQL\Type\Definition\Type; public function toGqlType() { return [ 'type' => GraphQL::string(), 'args' => [ // ] ]; } ``` If you need to register any types, the fieldtype can do that in the `addGqlTypes` method: ```php public function addGqlTypes() { // A class that extends Rebing\GraphQL\Support\Type $type = MyType::class; // or `new MyType;` GraphQL::addType($type); } ``` ## Laravel package Under the hood, Statamic uses the [rebing/graphql-laravel](https://github.com/rebing/graphql-laravel) package. By default, the integration should feel seamless and you won't even know another package is being used. Statamic will perform the following automatic configuration of this package: - Setting up the `default` schema to Statamic's. - Disabling the `/graphiql` route (since we have our own inside the Control Panel) However, you're free to use this package on its own, as if you've installed it into a standalone Laravel application. If Statamic detects that you've published the package's config file (located at `config/graphql.php`), it will assume you're trying to use it manually and will avoid doing the automatic setup steps mentioned above. If you'd like to use Statamic's GraphQL schema within the config file (maybe you want a different default, and want Statamic's one at `/graphql/statamic`) you can use the `DefaultSchema` class. ```php [ 'schemas' => [ 'statamic' => \Statamic\GraphQL\DefaultSchema::class ] ] ``` ## Authorization By default, all queries are allowed by anyone. We plan to add native features in the future. You can define custom authorization logic for any query by providing a closure to the static `auth` method. ```php EntriesQuery::auth(function () { return true; // true authorizes, false denies. }); ``` :::warning Per-request authorization logic is **not safe to cache**. Statamic's response cache keys on the query and variables only — not on the user or request — so the first response is served to everyone after it. If you use `::auth()` closures or per-user authorization, [disable the cache](#disabling-caching). ::: ## Authentication Out of the box, the GraphQL API is publicly accessible. You can restrict access to the API by adding the `STATAMIC_GRAPHQL_AUTH_TOKEN` key to your `.env` file. It should be set to a long, random string. ```php STATAMIC_GRAPHQL_AUTH_TOKEN=a-long-random-string ``` Then, when you make requests to the GraphQL API, you'll need to include the token in the `Authorization` header, like this: ```curl curl -X GET "https://example.com/graphql" \ -H "Authorization: Bearer a-long-random-string" \ -H "Accept: application/json" -d '{"query": "{ping}"}' ``` ### Authenticating users If you want to authenticate based on users, we recommend using [Laravel Sanctum](https://laravel.com/docs/master/sanctum) instead. To use Sanctum, you'll need to [store users in the database](/tips/storing-users-in-a-database) and add the `auth:sanctum` middleware in the `graphql.php` config. ```php // config/statamic/graphql.php 'middleware' => [ 'auth:sanctum', ], ``` :::warning When responses vary per authenticated user, you must [disable the response cache](#disabling-caching). The default cache is shared across all clients and does not account for the request's user, so one user's data can be served to another. ::: ## Custom fields You can add fields to certain types by using the `addField` method on the facade. The method expects the [type](#types) name, the field name, and a closure that returns a GraphQL field definition array. For example, if you wanted to include a thumbnail from an asset field named `image`, you could do that here. You can even have arguments. In this example, we'll expect the width of the thumbnail to be passed in. ```php use GraphQL\Type\Definition\Type; use Statamic\Facades\GraphQL; use Statamic\Facades\Image; use Statamic\Facades\URL; GraphQL::addField('EntryInterface', 'thumbnail', function () { return [ 'type' => GraphQL::string(), 'args' => [ 'width' => [ 'type' => GraphQL::int(), ] ], 'resolve' => function ($entry, $args) { $asset = $entry->image; $url = Image::manipulate($asset)->width($args['width'])->build(); return URL::makeAbsolute($url); } ]; }); ``` ```graphql { entry(id: 1) { thumbnail(width: 100) } } ``` ```json { "entry": { "thumbnail": "http://yoursite.com/img/asset/abc123?w=100" } } ``` The closure you pass to the method should return a GraphQL field definition array. You may add custom fields to the following types and any of their implementations: - `EntryInterface` - `PageInterface` - `TermInterface` - `AssetInterface` - `GlobalSetInterface` ## Caching GraphQL uses a basic whole-response cache by default. Each query/variables combination's response will be cached for an hour. You may customize the cache expiry in `config/statamic/graphql.php`. ```php 'cache' => [ 'expiry' => 60, ], ``` :::warning The cache key is based on the **query and variables only** — not the authenticated user or request context. This means any per-request authorization (via [`::auth()`](#authorization) closures or [per-user authentication](#authenticating-users) like Sanctum) is **not safe to cache**, because the first response will be served to every subsequent client regardless of who they are. A global [auth token](#authentication) is safe — the request is rejected before it ever reaches the cache, so everyone who gets through has identical access. Per-user auth is not. If any of your queries return data that depends on who's asking, [disable caching](#disabling-caching). ::: ### Cache invalidation Cached responses are automatically invalidated when content is changed. Depending on your GraphQL usage and blueprint schema, you may also wish to ignore specific events when invalidating. ```php 'cache' => [ 'expiry' => 60, 'ignored_events' => [ \Statamic\Events\UserSaved::class, \Statamic\Events\UserDeleted::class, ], ], ``` ### Disabling caching If you wish to disable caching altogether, set `cache` to `false`. ```php 'cache' => false, ``` ## Custom middleware You may add custom middleware, which are identical to any other Laravel middleware class. They will be executed on all GraphQL requests (unless another middleware, e.g. caching, prevents it). Use the `handle` method to perform some action, and pass the request on. ```php use Closure; class MyMiddleware { public function handle($request, Closure $next) { // do something return $next($request); } } ``` You may add your own middleware to Statamic's default schema. You can add them to the config file, which makes sense for app specific middleware: ```php // config/statamic/graphql.php 'middleware' => [ MyMiddleware::class ] ``` Or, you may use the `addMiddleware` method on the facade, which would be useful for addons. ```php GraphQL::addMiddleware(MyMiddleware::class); ``` ## Troubleshooting ### "Cannot query field" error If you see an error like `Cannot query field "entries" on type "Query"`, this likely means you haven't enabled that query. See [Enable GraphQL](#enable-graphql). After enabling it, you may need to clear your cache as the request would probably have been cached. ================================================ FILE: content/collections/pages/home.md ================================================ --- id: 5ee53e1b-8933-4b2e-a72d-af09f2b32600 blueprint: home title: Home meta_title: 'Learn Statamic' intro: 'This is where the learning begins and the veterans return for their references.' template: home content_width: flex-1 breadcrumb_title: 'Learn Statamic' tiles: - id: m8fsi7v5 tile_image: tiles/floppy-disk.png tile_title: 'Installing Statamic' tile_description: 'Let’s get Statamic installed and start tinkering around.' type: tile enabled: true flush_image: false hue_rotate: false tile_link: 'entry::ab08f409-8bbe-4ede-b421-d05777d292f7' - id: m8fsmcva tile_image: tiles/cat.png tile_title: 'Learn by Watching' tile_description: 'Learn Statamic with Jack’s free Laravel Creator Series.' type: tile enabled: true flush_image: false hue_rotate: hue_rotate_1 tile_link: 'https://learnstatamic.com' - id: m8fszcgf tile_image: tiles/modern-people.png tile_title: 'Discord Community' tile_description: 'Hang out with exceptional devs & designers 24/7.' type: tile enabled: true flush_image: false hue_rotate: false tile_link: 'https://statamic.com/discord' - id: m8ft268a tile_image: tiles/computer.png tile_title: 'Antler Templating Docs' tile_description: 'Start unlocking the mighty flexible powers of Antlers.' type: tile enabled: true flush_image: false hue_rotate: false tile_link: 'entry::d37b2af2-f2bf-493a-9345-7087fb5929ce' - id: m8ft2vpz tile_image: tiles/cd.png tile_title: 'UI Component Library' tile_description: 'Level up your custom addons and control panel extensions' type: tile enabled: true flush_image: false hue_rotate: false tile_link: 'https://ui.statamic.dev' - id: m8ft8fb1 tile_image: tiles/stereo.png tile_title: 'Recent Doc Updates' tile_description: 'Help keep your Statamic superpowers up-to-date.' type: tile enabled: true flush_image: false hue_rotate: false tile_link: 'entry::a77eb282-e050-4288-b83d-789840e245fd' advert_override: 972d7159-e76a-4817-a441-65d965d8c794 --- ================================================ FILE: content/collections/pages/hooks.md ================================================ --- id: 900414a8-f73a-46f0-82bd-b607767f5d5d blueprint: page title: 'Hooks' intro: 'Statamic allows you to hook into specific points in PHP logic and perform operations using Pipelines.' --- :::warning This page is about PHP-based hooks. We also have [JavaScript-based hooks](/extending/js-hooks), which work differently. ::: ## About Closures may be registered allowing you to "hook" into a specific point in PHP's lifecycle. These closures are added to a pipeline. Hooks may be located in tags, fieldtypes, and so on. At some point, a payload is send through the pipeline, allowing any registered closures to inspect or modify the payload, then finally gets sent back to the origin. ## How to use hooks For example, the `collection` tag will query for entries, then run the `fetched-entries` hook, passing all the entries along. Your hook may modify these entries. ```php // app/Providers/AppServiceProvider.php use Statamic\Tags\Collection; Collection::hook('fetched-entries', function ($entries, $next) { // Modify the entries... $modified = $entries->take(3); // Pass them along to the next registered closure. return $next($modified); }); ``` It's also possible to wait until all the other closures in the pipeline have completed. To do that, pass it along to the next closure _first_. For example, maybe you need to get all the ids of the entries that will be output. By passing along to the other closures first, it will give them a chance to manipulate it. In the example above, it would take the first 3 entries. Now in this hook we'll be getting 3 ids rather than the full amount the tag was originally going to output. ```php // app/Providers/AppServiceProvider.php use Statamic\Tags\Collection; Collection::addHook('fetched-entries', function ($entries, $next) { // Pass the payload along to the next registered closures. $entries = $next($entries); $ids = $entries->pluck('id'); // You'll still need to return it! return $entries; }); ``` ### Scope The closure is scoped to the class where the hook was triggered. The `$this` variable will be the class itself, and will act as if you're in the class so you can call protected methods, as well as any macroed methods. ```php Tag::addHook('name', function ($payload, $next) { // {{ tag foo="bar" }} $this->params->get('foo'); // bar }); ``` ## Available hooks ### All tags: `init` Triggered after the tag has been initialized. The payload is `null`. ### Collection tag: `fetched-entries` Triggered just after completing the query. The payload will either be an `EntryCollection` or a `Paginator`, depending on whether the `paginate` parameter was used. ### Form tag: `attrs` Triggered when building the opening form tag. The payload is an array containing two properties: - 'attrs' - an array containing the currently calculated list of attributes for the opening <form> tag. Modifications to this array will affect the rendered form tag - e.g. it can be used to add attributes to the form tag. - 'data' - the data assembled about the form (config, blueprint, sections etc.) ### Form tag: `after-open` Triggered immediately after the opening form tag. The payload is an array containing two properties: - 'html' - A string containing the rendered markup of the form so far. Modifications to this string will affect the final rendered markup. - 'data' - the data assembled about the form (config, blueprint, sections etc.) ### Form tag: `before-close` Triggered immediately before the closing form tag. The payload is an array containing two properties: - 'html' - A string containing the rendered markup of the form so far. Modifications to this string will affect the final rendered markup. - 'data' - the data assembled about the form (config, blueprint, sections etc.) ### Augmentation: `augmented` Triggered when a new augmented instance is made. The payload will be the object being augmented (eg. `Entry` / `Term`). ### Asset Thumbnails: `asset` Triggered when generating a thumbnail for an asset in the Control Panel. ```php use Statamic\Http\Resources\CP\Assets\FolderAsset as FolderAssetResource; FolderAssetResource::hook('asset', function ($payload, $next) { $payload->data->thumbnail ??= "https://custom-thumbnail-cdn.com/{$this->resource->id()}"; return $next($payload); }); ``` ### Entry Creation Values: `creating-entry` Triggered when showing the entry creation form in the Control Panel. The payload will be an object with `entry` and `values` properties. You can modify `values` to change the default values on screen. ```php use Statamic\Http\Controllers\CP\Collections\EntriesController; EntriesController::hook('creating-entry', function ($payload, $next) { if ($payload->entry->collection()->handle() == 'my-collection') { $payload->values = [...$payload->values, 'title' => 'testing 123']; } return $next($payload); }); ``` ### Entry Index Query: `query` Triggered before the index query for the Entries listing table is executed. The payload will be an object with `query` and `collection` properties. ```php use Statamic\Hooks\CP\EntriesIndexQuery; EntriesIndexQuery::hook('query', function ($payload, $next) { $payload->query; // a QueryBuilder instance $payload->collection; // a Collection instance return $next($payload); }); ``` ### Bard: `augment` Triggered while the Bard fieldtype is being augmented. The payload will be an array of the Bard's content. ### Bard: `process` Triggered when the `process` method is called on the Bard fieldtype (when saving a Bard field in the Control Panel). The payload will be an array of the Bard's content. ### Bard: `pre-process` Triggered when the `preProcess` method is called on the Bard fieldtype (when preparing the Bard field for the publish form). The payload will be an array of the Bard's content. ### Bard: `pre-process-index` Triggered when the `preProcessIndex` method is called on the Bard fieldtype (when preparing the Bard field for a listing column). The payload will be an array of the Bard's content. ### Bard: `pre-process-validatable` Triggered when the `preProcessValidatable` method is called on the Bard fieldtype (when preparing the field for validation). The payload will be an array of the Bard's content. ### Bard: `preload` Triggered when the `preload` method is called on the Bard fieldtype (when preparing the `meta` prop for the publish form). The payload will be an array of the Bard's content. ### Bard: `extra-rules` Triggered when the `extraRules` method is called on the Bard fieldtype (when gathering validation rules). The payload will be an array of the Bard's content. ### Bard: `extra-validation-attributes` Triggered when the `extraValidationAttributes` method is called on the Bard fieldtype (when gathering validation attributes). The payload will be an array of the Bard's content. ### Static Cache Warming: `additional` Triggered when the `static:warm` command is run. This hook allows you to warm additional URIs during the static warming process. For more information about this hook, see the docs on [Static Caching](/static-caching#warming-additional-urls). ### Multisite Command: `after` Triggered at the end of the `multisite` command. This hook allows you to run code when an app is being converted from a single-site to a multi-site. The payload is `null`. ### GetItemsContainingData: `additional` Triggered when updating asset and term references. This hook allows you to return additional content to be updated. You should return a [`LazyCollection`](https://laravel.com/docs/13.x/collections#lazy-collections). ## Triggering your own hooks You may want to trigger your own hook pipeline so that others may use it. To do this, you may use the `runHooks` method from the `Hookable` trait, passing the hook name and a payload. Once any hook closures have finished running, the payload will be returned back from it. ```php use Statamic\Support\Traits\Hookable; class YourClass { use Hookable; public function something() { $result = $this->runHooks('hook-name', $payload); } } ``` Now others will be able to call `hook` on your class to register their hook: ```php YourClass::hook('hook-name', function ($payload, $next) { // ... return $next($payload); }); ``` :::tip Tag classes already `use Hookable` so you can simply use `$this->runHooks()` without importing anything. ::: ================================================ FILE: content/collections/pages/image-manipulation.md ================================================ --- id: 245068a1-1900-4774-a3ba-29192dc9acff blueprint: page title: 'Image Manipulation (Glide)' intro: Statamic uses [Glide](https://glide.thephpleague.com) to manipulate images – from resizing and cropping to adjustments (like sharpness and contrast) and image effects (like pixelate and sepia). --- ## Route The route controls where your Glide images will be served. ```php 'image_manipulation' => [ 'route' => 'img' ] ``` By default your Glide images will be served from `'/img/...'` but you are free to change that. Perhaps if you intend to have some actual images stored in the `img` directory. :::tip This route setting may become irrelevant when using customized [caching options](#caching) explained further down this page. ::: ## Presets Presets are pre-configured sets of manipulations that can be referenced at a later time. They are managed in `config/statamic/assets.php` as an array that holds a list of named presets and their desired parameters. ```php 'image_manipulation' => [ 'presets' => [ 'thumbnail' => [ 'w' => 300, 'h' => 300, 'q' => 75 ], 'hero' => [ 'w' => 1440, 'h' => 600, 'q' => 90 ], ], ], ``` All standard [Glide API parameters](https://glide.thephpleague.com/3.0/api/quick-reference/) are available for use in presets. ## Drivers Out of the box, Glide will use the [GD](https://www.php.net/manual/en/book.image.php) library for manipulating images. However, you can also use [ImageMagick](https://imagemagick.org) (which requires the `imagick` PHP extension) or [libvips](https://github.com/libvips/php-vips). You can change the driver in your `config/statamic/assets.php` file: ```php 'driver' => 'gd', // or 'imagick', or a custom driver class name ``` To learn more about the available drivers, please refer to the [Glide documentation](https://glide.thephpleague.com/3.0/config/image-driver/). ## Glide tag Each named preset can be referenced with the `preset` parameter on the [Glide tag][glide-tag]: ::tabs ::tab antlers ```antlers {{ glide:thumbnail preset="thumbnail" }} {{ glide:hero_image preset="hero" }} ``` ::tab blade ```blade ``` :: ### Generate on upload When uploading an image asset, any configured presets will be generated so they're ready when you need to reference them, e.g. in the Glide tag. By default, all presets are generated, however you can [customize this per-container](#customize-glide-preset-warming). You may also choose to disable image generation on upload completely: ```php 'image_manipulation' => [ 'generate_presets_on_upload' => false, ], ``` ### Generate manually You may want to generate the presets manually (for example after you changed the config, and you already uploaded the images, or if you've disabled generation on upload) on the command line: ```bash php please assets:generate-presets ``` ### Process source images Sometimes you may wish to process your actual source images on upload. For example, maybe you need to enforce maximum dimensions on extremely large images in order to save on disk space. To do this, first configure an image manipulation preset in `config/statamic/assets.php` for this purpose: ```php 'image_manipulation' => [ 'presets' => [ 'max_upload_size' => ['w' => 2000, 'h' => 2000, 'fit' => 'max'], ], ], ``` Then in your asset container settings, you can configure uploads to use this preset:
    Glide Process Source Images Glide Process Source Images
    :::tip The `fit` is the important part for this one. Using `max` will ensure images smaller than those dimensions will not be upscaled - only larger images will be resized. ::: ### Customize preset warming As mentioned [above](#presets), Statamic will generate images for all of your configured presets on upload. (i.e. "warming" the generated images). By default, Statamic will do this "intelligently", which means it'll generate all presets except for the one used for source processing:
    Glide Intelligently Warm Presets Glide Intelligently Warm Presets
    However, you may wish to configure which presets are warmed in your asset container settings (or leave this option blank to disable warming altogether):
    Glide Warm Specific Presets Glide Warm Specific Presets
    :::tip If you have a preset that's only going to be used with images in one particular container, you should customize which ones are used so your server doesn't have to waste resources on generating and storing images that won't get used. ::: ## Caching Out of the box, Glide will "just work". However, you may want to adjust its caching methods for better performance. In the context of Glide, the "source" is the filesystem where the original images are kept, and the "cache" is the filesystem where it saves the manipulated images. ### Default (Dynamic) The default behavior is for the cache to be "disabled" or "dynamic". ```php // config/statamic/assets.php 'image_manipulation' => [ 'route' => 'img', 'cache' => false, ] ``` From a user's point of view, the "cache" is disabled, however technically it's just located at `storage/statamic/glide`. The [Glide tag][glide-tag] will output URLs to the configured Glide [route](#route). When one of these URLs are visited, Statamic will use Glide to perform the transformation. :::tip When using this method, since the Glide tag only needs to generate URLs, the load time of the page will be faster, but the initial load time of each image request will be slower. ::: :::tip Be sure to set `STATAMIC_STACHE_WATCHER=false` in your `.env`. ::: ### Custom path (static) The next level of caching would be to specify a custom, publicly accessible location for the images to be generated. ``` php // config/statamic/assets.php 'image_manipulation' => [ 'route' => 'img', 'cache' => true, 'cache_path' => public_path('img'), ] ``` When using this setting, the [Glide tag][glide-tag] will _actually generate_ the images instead of just outputting a URL. Since the images are generated to a publicly accessible location, the next time a user visits the image URL, the static image would be served directly by the server, and would not need to be touched by PHP or Statamic. :::tip When using this method, since the Glide tag has to generate the images, the initial load time of the page will be slower. ::: ### Custom disk (CDN) You may choose to save your cached Glide images to somewhere CDN based, like Amazon S3 or DigitalOcean Spaces. Instead of specifying `true` as mentioned above, you can point to a filesystem disk. ```php // config/statamic/assets.php 'image_manipulation' => [ 'cache' => 'glide', ], ``` ```php // config/filesystems.php [tl! **] 'disks' => [ // [tl! **] 'glide' => [ // [tl! **] 'driver' => 's3', // [tl! **] 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION'), 'bucket' => env('AWS_BUCKET_GLIDE'), // [tl! **] 'url' => env('AWS_URL_GLIDE'), // [tl! **] 'endpoint' => env('AWS_ENDPOINT'), 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), 'visibility' => 'public', // [tl! **] ], ] ``` :::tip Make sure that the `visibility` is `public` and that the `url` points to the correct location. ::: :::warning Don't use the same disk, bucket or url as your source images. If you were to clear your Glide cache (e.g. when using the `glide:clear` command) the whole disk will be emptied. ::: ## Path cache store Before Glide tries to generate an image, it will look into the filesystem to determine whether the image has already been generated. This will prevent the need for the image to be needlessly re-generated. However, when using the [Custom Disk CDN](#custom-disk-cdn) caching option with a service like Amazon S3 for example, Glide will need to make an API call just to be able to check if a file exists. This would cause a slowdown. To alleviate this problem, Statamic will keep track of whether the images have already been generated in its own separate cache. This cache is separate from your application cache. Running `php artisan cache:clear` will **not** clear this Glide cache. This allows the Glide cache to persist through deployments or other scenarios where you might clear your application cache. It will be cleared when running `php please glide:clear`. By default, this cache will be located in your filesystem with the storage directory. If you would like to customize it, you can create a new store named `glide` in your `config/cache.php` configuration file. For example: ```php 'stores' => [ 'glide' => [ 'driver' => 'redis', 'connection' => 'glide', ], ] ``` In this example, you would also need to create a Redis database named `glide` in your `config/database.php` configuration file: ```php 'redis' => [ 'glide' => [ 'url' => env('REDIS_URL'), 'host' => env('REDIS_HOST', '127.0.0.1'), 'password' => env('REDIS_PASSWORD'), 'port' => env('REDIS_PORT', '6379'), 'database' => env('REDIS_GLIDE_DB', '2'), ], ], ``` When using the [database driver](https://laravel.com/docs/13.x/cache#prerequisites-database), make sure to specify the connection and table, like so: ```php 'glide' => [ 'driver' => 'database', 'connection' => 'mysql', 'table' => 'cache', ], ``` ## Custom hash By default, Glide-generated images are saved into a directory named after an md5 hash of the manipulation parameters, resulting in a path like this: ``` containers/assets/path/to/image.jpg/0638baede3a7fc1a91f605e095ab74cc/image.jpg ``` You may customize how that hash is generated — for example, to produce human-readable paths for debugging, QA, or aesthetics. Register a closure in a service provider's `boot` (or `register`) method: ```php use Statamic\Facades\Glide; public function boot() { Glide::generateHashUsing(function (string $source, array $params) { return collect($params) ->sortKeys() ->map(fn ($value, $param) => "$param-$value") ->join('-'); }); } ``` With the closure above, this tag: ::tabs ::tab antlers ```antlers {{ glide:myimage width="100" height="50" fit="crop" quality="50" }} ``` ::tab blade ```blade ``` :: ...would generate to a path like this: ``` containers/assets/path/to/image.jpg/fit-crop-h-50-q-50-w-100/image.jpg ``` :::warning Your closure must return a string that is unique per combination of parameters. Collisions will cause the wrong image to be served. ::: ## Clearing the cache You may manually clear the Glide cache by running the following command: ``` php please glide:clear ``` This will **delete all the files** within your Glide cache filesystem location, as well as clearing the [path cache](#path-cache-store). [glide-tag]: /tags/glide ================================================ FILE: content/collections/pages/installing-a-starter-kit.md ================================================ --- id: c51a5de8-4b02-4240-8195-3ff7987c43cf title: 'Installing a Starter Kit' intro: Installing a Starter Kit is a pretty simple thing, but like with many things in life, there are a few different ways you can do it. Let's cover them all. template: page blueprint: page nav_title: Installing --- ## Read this first Most (but not all) Starter Kits are intended to be used in a brand new, empty site. Be sure to read each Kit's documentation before installing into an existing site so you know what to expect and how to get the most out of it. ## Installing from the command line You can spin up a **new** install of Statamic along with a Starter Kit at the all in one command by using the [Statamic CLI Tool](https://github.com/statamic/cli): ``` shell statamic new my-site vendor/starter-kit ``` You can alternatively install a Starter Kit into an _existing site_ by running the following command while inside that install's root directory: ``` shell php please starter-kit:install vendor-name/starter-kit-name ``` ### Installing a paid starter kit {#paid} If you are installing a paid starter kit, you will be prompted to purchase and/or validate a single-use license. Once successfully installed, this license will be marked as used and cannot be used on future Statamic sites. ### When to clear your site first If you are installing into a fresh Statamic installation (a pre-built site, for example), you may wish to clear your site of any sample or placeholder content first. If you are installing a more _modular_ or functionality-driven type Starter Kit into an existing Statamic site (like an icon set or e-commerce checkout), you will want to skip this step! The installer will ask you if you wish to clear your site first, but you can also force clear by running with the `--clear-site` install option. ### Installing without dependencies If you wish to install without bundled dependencies, you can run with the `--without-dependencies` install option. ================================================ FILE: content/collections/pages/installing.md ================================================ --- title: How to install Statamic breadcrumb_title: Install intro: Because Statamic is a **self-hosted platform**, there are many different ways to get started. We recommend using whichever approach you're most comfortable with. template: installing id: ab08f409-8bbe-4ede-b421-d05777d292f7 blueprint: page content_width: max-w-4xl hide_toc: true --- ================================================ FILE: content/collections/pages/javascript-frameworks.md ================================================ --- id: 131259a5-2072-49d8-9ea4-2099e0338e2f blueprint: page title: 'JavaScript Frameworks' intro: 'There are many different approaches you could take to pass data to JavaScriptLand. Here are some suggestions on how to fetch, format, and hydrate (inject data) typical JavaScript components.' template: page nav_title: 'Front-End Frameworks' updated_by: 3a60f79d-8381-4def-a970-5df62f0f5d56 updated_at: 1632512027 --- The examples below use [Vue.js](https://vuejs.org/) as the framework of choice, but these techniques will apply to most JavaScript frameworks. ## Pass all page data directly to a component This is probably the simplest possible method. You can encode all the page data into JSON and inject it directly into your component. The downside is that you'll be exposing all that data to the client-side, if that's a concern for your particular site. ::tabs ::tab antlers ```antlers ``` ::tab blade ```blade ``` :: ## Assemble selective JSON inside Antlers/Blade and pass to components via props This method is simple, best used for one-off situations. It provides you control over exactly what data you want to pass to your components, but is too messy to be used at a larger scale. ::tabs ::tab antlers ```antlers ``` ::tab blade ```blade ``` :: ## Fetching data from a collection This method is used to fetch _any_ entry-based data, not just that available on the current page. ```vue ``` :::tip [Live Preview](/live-preview) only has the current page's data available to it. Trying to query collection data **will not work**. ::: ## The Content API [The Content REST API](/rest-api) can be used on its own, or in conjunction with the above methods. Here is a simple example component that fetches data using the asynchronous `created()` function. This data can then be used in the component or passed down to child components. The example uses the standard `Fetch` method but you can use any AJAX library (Axios, etc). ```vue ``` ## Custom view models It is also possible to create [a view model](/view-models) which will return only the data you require. However, this requires PHP knowledge. ================================================ FILE: content/collections/pages/js-events.md ================================================ --- title: Event Bus id: b7519137-73b6-46c7-8432-da7725b1d9b4 --- For situations where emitting an event to the parent component doesn't make sense, Statamic has a global event bus. You can emit and listen to events directly on this which will be available to all Vue components. ``` js import { events } from '@statamic/cms/api'; // Emit from some component... events.$emit('event.name'); // Listen for it in another component... events.$on('event.name'); ``` :::tip The event bus is intended to be used for Vue component communication. If you want to listen for Statamic driven "events", check out [Hooks](/extending/hooks). ::: ================================================ FILE: content/collections/pages/js-hooks.md ================================================ --- title: JavaScript Hooks nav_title: Hooks intro: | Statamic allows you to hook into specific points in JavaScript and perform asyncronous operations using [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). id: fc136da3-ba46-46e1-8443-e345d5b548ac --- :::warning This page is about JavaScript-based hooks. We also have [PHP-based hooks](/extending/hooks), which work differently. ::: ## About Hooks are programmable interfaces which you can use to modify or extend already existing JavaScript code inside the Control Panel. Normally they work like this: 1. Statamic code is running 2. Statamic code checks for existing Hooks 3. Your custom hook was found 4. The code inside your custom hook will be run 5. Statamic code continues to run (with modfied data etc.) ## Prerequisites Before you start using hooks, you need to register the corresponding javascript file. Please follow the guide on [Adding CSS and JS assets](/extending/control-panel#adding-css-and-js-assets). ## How to use hooks For example, the `entry.saving` hook allows you to pause saving to perform an action: ```js Statamic.$hooks.on('entry.saving', (resolve, reject) => { if (confirm('Are you sure you want to save this entry?')) { // Continue with the save action. resolve(); } else { // Cancel the save action. You can provide the error message. reject('You chose not to publish.'); } }); ``` If you intend to work with another promise, make to resolve or reject once it's done: ```js Statamic.$hooks.on('entry.saving', (resolve, reject) => { return axios.get('/something') .then(resolve) .catch(() => reject('It broke')); }); ``` Some hooks may provide you with a payload containing additional data. For example, the `entry.saving` hook provides you with the collection handle and form values. ```js Statamic.$hooks.on('entry.saving', (resolve, reject, payload) => { console.log(payload.collection); // blog console.log(payload.values); // { title: "My Post", content: "Post Content" } }); ``` :::best-practice When you `reject` a hook, any other code using that hook will not be executed. Unless your intention is to stop the execution chain, you should always `resolve`, even when your code does nothing. ``` js Statamic.$hooks.on('example', (resolve, reject) => { if (somethingShouldHappen) { doSomething(); } resolve(); }); ``` ::: ## Available hooks ### `entry.saving` Triggered when you click save on the publish form. You can use `reject()` to prevent the request. Payload contains the collection handle, entry reference and form values. ### `entry.saved` Triggered when you click save, but after the request has finished. Payload contains the collection handle, entry reference and the Axios response. ### `entry.publishing` Triggered when revisions are enabled, and you click publish in the publish action stack. You can use `reject()` to stop the request. Payload contains the collection handle and revision message. ### `entry.published` Triggered when revisions are enabled, but after the request has finished. Payload contains collection name, revision message, and the Axios response. ### `global-set.saving` Triggered when you click save on the publish form. You can use `reject()` to prevent the request. Payload contains the global set handle and form values. ### `global-set.saved` Triggered when you click save, but after the request has finished. Payload contains the global set handle and the Axios response. ### `term.saving` Triggered when you click save on the publish form. You can use `reject()` to prevent the request. Payload contains the taxonomy handle, term reference and form values. ### `term.saved` Triggered when you click save, but after the request has finished. Payload contains the taxonomy handle, term reference, and the Axios response. ### `user.saving` Triggered when you click save on the publish form. You can use `reject()` to prevent the request. Payload contains the user reference and form values. ### `user.saved` Triggered when you click save, but after the request has finished. Payload contains the user reference and the Axios response. ================================================ FILE: content/collections/pages/keyboard-shortcuts.md ================================================ --- title: 'Keyboard Shortcuts' intro: 'Improve usability by adding keyboard shortcuts.' id: efcca509-5690-4201-88d7-74c542bb9900 --- You may add keyboard shortcuts with a simple syntax, based on the [Mousetrap](https://craig.is/killing/mice) library. ## Basic usage Bind a keyboard shortcut in a similar way to how you would with Mousetrap. You will get a reference to a Binding object. To unbind, destroy it. ``` js import { ref, onMounted, onBeforeUnmount, getCurrentInstance } from 'vue'; const binding = ref(null); const save = () => { // } onMounted(() => { binding.value = Statamic.$keys.bind('mod+s', save); }); onBeforeUnmount(() => { binding.value?.destroy(); }); ``` ## Unbinding If you are binding a keyboard shortcut in a component that may disappear - perhaps it's in a stack or modal - you should destroy it once you're done. When destroying the binding, it will revert back to the previous binding if one existed. For example: If you're on a form which already uses mod+s to save it, and you open your component which re-binds mod+s, when you destroy your binding, the previous form's binding will kick back into gear. ## Available methods ``` js this.$keys.bind(keys, fn); ``` Creates a keyboard shortcut binding. First argument is a [key sequence](#key-sequences), or array of key sequences. Second argument is a function to be executed. The `Binding` object is returned. ``` js this.$keys.bindGlobal(keys, fn); ``` Creates a global keyboard shortcut binding. Works the same as `bind`, except the shortcut will work inside text fields. ## Key sequences A sequence can be: - a single key. eg. `/` - multiple keys together: eg. `shift+/` - an actual sequence of keys: eg. `up up down down` ## Available keys For modifier keys you can use `shift`, `ctrl`, `alt`, or `meta`. You can substitute `option` for `alt` and `command` for `meta`. Other special keys are `backspace`, `tab`, `enter`, `return`, `capslock`, `esc`, `escape`, `space`, `pageup`, `pagedown`, `end`, `home`, `left`, `up`, `right`, `down`, `ins`, `del`, and `plus`. Any other key you should be able to reference by name like `a`, `/,` `$,` `*,` or `=.` :::tip You can use `mod` to mean both `ctrl` on Windows and `cmd` on Mac. This saves you from having to define two separate sequences. ::: ================================================ FILE: content/collections/pages/knowledge-base.md ================================================ --- id: 2d6381b8-dc0a-4a5d-b750-1a9dd7c0bb14 title: 'Knowledge Base' blueprint: link redirect: url: '@child' status: 301 --- ================================================ FILE: content/collections/pages/laravel-7-to-8.md ================================================ --- id: ec130472-4f44-4e7e-8dce-71d0c93e8fef blueprint: page title: 'Upgrade from Laravel 7 to 8' intro: 'A quick guide for upgrading from Laravel 7 to 8.' template: page --- On a fundamental level, a Statamic website is a Laravel application with Statamic installed into it. When you upgrade Statamic, you're _just_ upgrading Statamic — Laravel is not automatically upgraded at the same time. Major Statamic releases may require newer versions of Laravel, which result in the need to upgrade the Laravel side of the application. There are many benefits to staying up to date with the latest versions of Laravel — security, performance, and compatibility with a wider range of Composer packages to name a few. :::warning Disclaimer **This guide is intended for "stock" Statamic sites.** That is, a site where you installed Statamic and haven't added or customized functionality on the Laravel side of the application (typically the `/app/` directory). If you've added custom functionality, this guide will still be helpful, just be mindful of anything you may have added or modified and be sure to account for it. ::: :::warning Another Disclaimer You should be performing your updates **locally**. Never update directly on production. ::: 1. First, your site should be version controlled with Git. These steps assume you'll be able to look at a git diff to make sure you don't remove important changes, dependencies, or routes. 1. Make sure all Composer dependencies are up to date by running `composer update`. ```bash composer update ``` 1. Run a `git commit` to start a clean slate. ```bash git commit -am "Updated Composer dependencies" ``` 1. Download a zip of a fresh `statamic/statamic` site. Head to the [3.2.7 GitHub release](https://github.com/statamic/statamic/releases/tag/v3.2.7), download `Source code (zip)`, and unzip it somewhere on your computer. 1. Delete (or move to another folder, or the trash) `app`, `bootstrap`, `config`, `routes`, `composer.json`, and `artisan`. 1. Copy `app`, `bootstrap`, `config`, `routes`, `composer.json`, and `artisan` from the zip into your project. 1. Look through the Git changes to see if anything was removed that you wanted to keep. For example: - Anything custom, like a Modifier or Controller, you may have added to the `app` directory - Configuration values you may have changed in the `config` directory - Additional dependencies in `composer.json` - Custom routes you may have configured in `routes/*` 1. Update your dependencies again now that you have an updated `composer.json`. ```bash composer update ``` 1. Give your site a thorough test. For most sites, that'll do it! If your site is more complex or is part of a larger Laravel Application, you can follow the [Laravel Upgrade guide](https://laravel.com/docs/8.x/upgrade) or use [Laravel Shift](https://laravelshift.com) to automate the upgrade for you. ================================================ FILE: content/collections/pages/laravel-cloud.md ================================================ --- id: 68d936b0-b1b0-431d-bbe0-a8356decf251 blueprint: page title: 'Deploying Statamic with Laravel Cloud' intro: |- Laravel Cloud is a fully managed infrastructure platform. It's relentlessly optimized for Laravel and PHP. It's our favorite way to deploy Statamic sites that need to scale. parent: c4f17d05-78bd-41bf-8e06-8dd52f6ec154 --- :::warning Currently, Statamic's [Git automation](/git-automation) doesn't work on Laravel Cloud. This may change in the future as Laravel Cloud continues to evolve. For now, we recommend moving [content](/tips/storing-content-in-a-database) and [users](/tips/storing-users-in-a-database) into the database, and moving assets [onto Laravel Cloud's object storage](#creating-an-object-storage-bucket) service. Alternatively, if you prefer to keep everything in flat files, you can disable the Control Panel and manually push any content changes from your local environment. ::: ## Creating your application Once you've created your [Laravel Cloud](https://app.laravel.cloud) account, click "New application" to get started. If it's your first application, you'll be asked to connect your Git provider of choice (GitHub, GitLab or Bitbucket). From there, select the repository you want to deploy, give it a name and pick the region where you want your application deployed.
    New application modal
    Upon creation, you can setup any "resources" needed for your application, like a database or object storage bucket.
    Application overview
    Make sure to click "Save" after making changes to your application's resources. ### Creating a database If you're storing content and users in the database (which), you'll need to create a database cluster in Laravel Cloud. You can do this from the environment overview page:
    New database cluster modal
    Once you've created your database cluster, you'll probably need to import data from an existing database you might have, whether that be locally or from a staging server. You can use a database GUI, like [TablePlus](https://tableplus.com/) to do this. 1. Open your existing database, select all of the tables, and right click "Export". Make sure to save your export as a `.sql` file. 2. Connect to your new Laravel Cloud database using the "View credentials" button.
    Database credentials modal
    If you're using TablePlus (or another GUI that supports it), you can open the database directly from the "Deeplink" tab. 3. Finally, import your database by right-clicking the tables list and selecting "Import -> From SQL File". From here, you can choose the `.sql` file you just exported. ### Creating an object storage bucket Since the [Git Automation](/git-automation) does not work on Laravel Cloud, we recommend moving assets to Laravel Cloud's object storage service. Laravel Cloud provides an S3-compatible filesystem, so you will need to install the S3 Flysystem driver in your project: ``` composer require league/flysystem-aws-s3-v3 "^3.0" --with-all-dependencies ``` Then, in Laravel Cloud, you can create a new bucket from the environment overview page:
    You will then be prompted to select a filesystem disk for the bucket, it should match a disk in your `config/filesystems.php` config. Once the bucket is created, you can upload existing assets using a tool like [Transmit](https://panic.com/transmit/), [Cyberduck](https://cyberduck.io/), or any similar app that supports S3-compatible filesystems. ## Deploying your application Now that you've got everything set up, all thats left to do is trigger your first deployment. 🚀 If you're using Vite to build CSS/JavaScript, make sure to uncomment the `npm` commands in your application's deployment settings. For more information about Laravel Cloud, please see its [documentation](https://cloud.laravel.com/docs). ## Static Caching You can't use full-measure static caching with Laravel Cloud, as there's no way to edit the underlying Nginx config. However, you can use [half-measure static caching](/static-caching#application-driver), which stores the cached HTML pages in your application's cache. In order for the static cache to persist between deployments, you should use a persistent cache driver like [`database` or `redis`](https://laravel.com/docs/master/cache#configuration). ## Troubleshooting ### Upstream sent too big header while reading response header from upstream You may encounter this error when submitting a form on the frontend, or updating content in the Control Panel. You can fix it by changing your application's session driver from `cookie` to another driver, like `database` or `redis`. You can find more information about session drivers on the [Laravel documentation](https://laravel.com/docs/13.x/session#introduction). ================================================ FILE: content/collections/pages/laravel-forge-1-click.md ================================================ --- id: 48c60d99-04e7-47f6-9576-aee1401fcb50 blueprint: page title: 'How to Install Statamic on Laravel Forge' nav_title: 'Laravel Forge' intro: "A full tutorial on how to install Statamic with Forge's 1-Click Installer. For this walk-through, we'll assume you have a [Forge](https://forge.laravel.com) account with a server provisioned." parent: ab08f409-8bbe-4ede-b421-d05777d292f7 --- The Laravel team have made installing Statamic exceedingly simple. Follow these ... steps, and you'll have a Statamic site running that you can log right into. :::tip If you _already have_ a Statamic site built, you should switch over to the [Deploying Statamic on Laravel Forge](/deploying/laravel-forge) guide. ::: ### 1. Create a new site Make sure to select "Statamic" from the "New site" dropdown. Then click on the "Use a starter kit" tab.
    Create site using a starter kit
    You'll first be asked to configure a domain. If you don't have one yet, you can use a `.on-forge.com` subdomain. Then, you can pick which Starter Kit you'd like to use. Only free/open-source Starter Kits are available through this workflow, so if you'd prefer one of the paid/commercial kits, you'll need to follow the [local install](/installing/laravel-herd) and [Deploy on Laravel Forge](/deploying/laravel-forge) guides.
    Create site using a starter kit
    Finally, set up an email and password and click "Create site". After creating your site, Forge will take a few seconds to configure the necessary services, like Nginx and PHP-FPM, then you'll be able to visit your new site. ### 2. Sign in to your new Statamic site Assuming you've pointed your DNS to this server, all that's left is to head to `yourdomain.com/cp` and sign in to the Statamic Control Panel. The site is yours.
    Statamic Login Screen Statamic Login Screen
    If you see this screen at /cp you've just earned 200 XP!
    ================================================ FILE: content/collections/pages/laravel-forge.md ================================================ --- id: 8fd95af9-f635-45bb-a3d1-1fa1db7be4a2 blueprint: page title: 'Deploying Statamic with Laravel Forge' intro: Laravel Forge provisions and deploys PHP applications on DigitalOcean, Vultr, Akamai, AWS Hetzner and other hosting platforms. It's our favorite way to deploy Statamic. parent: c4f17d05-78bd-41bf-8e06-8dd52f6ec154 --- Assuming you already have a [Laravel Forge](https://forge.laravel.com) account, the first thing to do is create a server. ## Creating a New Server
    Create new server
    You can host your site on Laravel VPS (which is billed on top of your Forge subscription), [DigitalOcean](https://m.do.co/c/6469827e2269), AWS, Hetzner, or even your own fresh Ubuntu server.
    Configure new server
    On the next screen, you'll be asked to configure your server. For most Statamic sites, you'll want to leave the type as "App server". You should pick the region closest to your users and select a server size suitable for your project. Once you've created your server, Forge will give you your server's sudo and database passwords. Keep these safe as you won't be able to retrieve them later. ## Creating a New Site The next step is to create a new site. Make sure to select "Statamic" from the "New site" dropdown to take advantage of some Statamic-specific optimizations.
    Create site page
    Select your Git repository, the branch you want to deploy, and configure a domain. If you don't have a domain yet, you can use a `.on-forge.com` subdomain. :::tip Note Zero downtime deployments are disabled by default for Statamic sites, due to the additional configuration required. Before enabling, please review our [Zero Downtime Deployments](/tips/zero-downtime-deployments) guide. ::: ## Deploying After creating your site, Forge will take a few seconds to configure the necessary services, like Nginx and PHP-FPM, then you'll be able to trigger your first deploy.
    Deployment logs
    ## Configuring Deployments You can customize your deployment script under `Settings -> Deployments`. Your deploy script will look something like this: ```shell cd /home/forge/forge-demo-znqnhr0d.on-forge.com git pull origin $FORGE_SITE_BRANCH $FORGE_COMPOSER install --no-dev --no-interaction --prefer-dist --optimize-autoloader # Prevent concurrent php-fpm reloads... touch /tmp/fpmlock 2>/dev/null || true ( flock -w 10 9 || exit 1 echo 'Reloading PHP FPM...'; sudo -S service $FORGE_PHP_FPM reload ) 9 Environment`, you may configure your site's environment variables. You'll find Statamic's variables near the bottom, prefixed with `STATAMIC_`.
    .env editor
    ================================================ FILE: content/collections/pages/laravel-herd.md ================================================ --- id: 61c8db2d-f7bf-4829-bafd-f8a4db5a9a57 blueprint: page title: 'Install Statamic Locally Using Laravel Herd' nav_title: Herd intro: 'Using Laravel Herd to run Statamic locally is the **easiest and fastest way** to get started with the best CMS out there. It is also very **beginner-friendly**.' parent: ab08f409-8bbe-4ede-b421-d05777d292f7 --- ## Overview Laravel Herd offers a straightforward approach to setting up your machine with all the necessary dependencies for PHP development, like a web server and Composer, and of course PHP itself. It's easier compared to using Homebrew, less error-prone, and blazingly fast. It's our preferred and recommended way to get started with Statamic and Laravel development on a Mac and Windows. :::watch https://www.youtube-nocookie.com/embed/MZZYTXSysrQ?si=7sMSYltGFHLndWcf Watch this guide as a video 🐘 ::: ## Prerequisites To install Herd and run Statamic locally you will need the following: - A Macintosh running macOS 12.0+ - A personal computer running Windows 10+ - That's it. ## Install Laravel Herd Installing Herd is super easy and the same process as with any other application. Just download the latest version on [herd.laravel.com](https://herd.laravel.com).
    Screenshot showing the macOS finder with the Herd app icon
    Drag'n'Drop It Like It's Hot
    After you open it for the first time, you will get prompted to type in your admin password so all the necessary files can be put in the right places.
    Screenshot showing macOS prompting for the user password to install Laravel Herd
    If you previously used Laravel Valet on Mac you can migrate to Herd and all settings, like linked projects and installed PHP versions, will be synced. Didn't use Valet before? Then you won't see this screen.
    Screenshot showing that Laravel Herd detected Valet being used before
    Next, you're presented with a screen saying that you can now use Herd, Composer, and more. The default location to place your projects in is `~/Herd` which you can change in the settings, if you like. You can also choose to automatically launch Herd on startup which we would recommend.
    Screenshot showing the welcome screen of Herd after a successful installation
    After that's done, Herd has been successfully installed and you now have a local PHP development environment on your machine.
    Screenshot showing the macOS menubar item of Laravel Herd with Hide the Pain Harold giving thumbs up and several elephant emojis
    Harold congratulates you from Hungary 🇭🇺
    If you want to install additional PHP versions or make your local sites available through [Expose](http://expose.dev/) via secure tunnels, open the Herd settings to get access to a range of different options.
    Screenshot showing Herd's settings pane
    ## Install Statamic CLI Next up on your journey to greatness is installing the Statamic CLI. To do this, run the following command in your terminal: ``` shell composer global require statamic/cli ``` Upon installation, you can now use the `statamic new` command to spin up fresh Statamic sites with a CLI setup wizard 🧙‍♂️ to guide you through a variety of settings and options. Our CLI is essentially a super fancy wrapper around the `composer create-project` command. You can choose to not install it. Though it offers a wide range of really neat features and we recommend it. ## Create a new site Let's take the last big step toward you enjoying all of Statamic's radness™. In your terminal, run `statamic new your-project-name` and follow the prompts to create a new site. The command will create the site **in the current directory** you're in. So with Herd on Mac, you should run this command from the `~/Herd` directory. If you encounter any issues running `statamic new`, like a `Command not found` error, have a look at [our tips on troubleshooting this](/troubleshooting/command-not-found-statamic).
    Screenshot showing the Kitty terminal emulator running the statamic new command with ASCII art
    Lime green and zesty spirit 🍋
    ## Access the site Sweet, if you did all the previous steps you should now be able to open your site at `http://your-project-name.test`.
    Screenshot showing a browser window with the welcome page of a fresh Statamic site Screenshot showing a browser window with the welcome page of a fresh Statamic site
    The Control Panel, Statamic's admin area, can be accessed at `/cp` where you can log in with the user you created during the CLI's setup wizard. ## What's Next Well done, you have turned your computer into a local PHP development environment, installed the Statamic CLI, and set up your first Statamic site! 🎉 Get creative, explore everything, and build something rad. Want to learn more about Statamic? Watch [our series on Laracasts](https://laracasts.com/series/learn-statamic-with-jack) or just continue to browse through the docs. For additional information on Laravel Herd, have a look at its [documentation](https://herd.laravel.com/docs). :::tip Use all of Statamic's Pro features while in development (like unlimited users, permissions, GraphQL, REST API, and more), by setting `'pro' => true` in `config/statamic/editions.php`. ::: ================================================ FILE: content/collections/pages/laravel.md ================================================ --- id: e48bde09-8957-401a-a2b4-ba7a4fd26d67 blueprint: page title: 'How to Install into an Existing Laravel Application' breadcrumb_title: Laravel intro: Statamic can be installed **into** an existing Laravel application and used to add new sections — like a blog or press release section — function as a headless CMS, or even manage existing data. parent: ab08f409-8bbe-4ede-b421-d05777d292f7 --- ## Overview There are many reasons why you might want to install Statamic into an existing Laravel application. You could use Statamic to: - handle all the marketing and "logged out" content for a SaaS app - add an easy-to-manage blog the whole team can update - manage existing data kinda like [Laravel Nova](https://nova.laravel.com/) (yes, [you can do that](/extending/repositories)) - run as a headless CMS and provide a REST API to your data - be a special comfort package for those tough projects even when you don't need it :::tip If you're starting a brand new project, it's much easier to use the [standard Statamic installation method](/installing/local). You'll get a bunch of things automatically set up for you, like a pages collection, views, etc. If you install Statamic _into_ Laravel, you're going to have to do those things manually. ::: ## Supported Versions of Laravel **Statamic 6 supports Laravel 12 and 13**. If you are on an earlier version of Laravel you can still use Statamic 5 or previous versions. Keep in mind that these version might not be supported by us anymore. For more details please have a look at our [release and support schedule](/knowledge-base/release-schedule-support-policy). :::warning Inertia 3 is not currently supported. If you've used Laravel's React, Vue or Svelte starter kits recently, it would be using Inertia 3. If you want to use Statamic 6 you will need to downgrade to Inertia 2. We plan to add support for Inertia 3 in Statamic 7. ::: ## Install Statamic There are 3 steps to follow to install Statamic into your Laravel app. 1. Run `php artisan config:clear` to make sure your config isn't cached. 2. In `composer.json`, add the following items: ``` json "scripts": { // [tl! **] "pre-update-cmd": [ // [tl! ++ **] "Statamic\\Console\\Composer\\Scripts::preUpdateCmd" // [tl! ++ **] ], // [tl! ++ **] "post-autoload-dump": [ // [tl! **] "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", "@php artisan package:discover --ansi", "@php artisan statamic:install --ansi" // [tl! ++ **] ], // [tl! **] } // [tl! **] ``` 3. Install `statamic/cms` with Composer. ``` shell composer require statamic/cms --with-dependencies ``` 4. Depending on how you set up users in your app, you might need to run a command to publish Statamic's auth migrations. ``` shell php please auth:migration ``` ## Adding Content When you install Statamic into Laravel this way, **no content or views are included**. You'll probably want to create a collection and some entries, as well as views and a layout in order to see things appear on the front-end of your site. ### Pages A common "catch-all" content scenario is to create a Pages collection which allows you to create a home page as well as any other pages in a tree structure. You get this when you install Statamic from scratch, but it's easy to set up yourself. The easiest way is to copy the `pages.yaml` file and the `pages` directory [from the `statamic/statamic` repository](https://github.com/statamic/statamic/tree/6.x/content/collections). Or, if you wanted to do it through the Control Panel: 1. Create a collection named `pages`. - Set the route to `{parent_uri}/{slug}` - Enable the "Orderable" toggle - Enabled the "Expect a root page" toggle 2. Create an entry. The first one will be your home page. ### Views Statamic routed URLs will expect views named `default` and `layout`. You will need to add those manually too. [Read more about how Statamic views and layouts work](/views) ## Database ### Content When you install Statamic into an existing Laravel application, content will be stored as flat files. If you'd prefer to store content in a database instead, please follow the ["Storing Content in a Database"](/tips/storing-content-in-a-database) guide. ### Users If you want to continue to keep users in a database, head over to [Storing Users in a Database in an Existing Laravel App](/tips/storing-users-in-a-database#in-an-existing-laravel-app) and follow those steps. Otherwise, the [Storing User Records](/users#storage) page should have instructions for the most common scenarios. ## New Statamic Directories After Statamic is installed, you'll have 3 new directories in your project: - `content/`, - `resources/users/` - `config/statamic/` :::tip **Statamic relies on a "catch-all" wildcard route to handle its URLs.** Any explicitly defined routes in your application will take precedence over those handled by Statamic. Be sure that you don't have _your own_ catch-all route or else nothing will ever be passed off to Statamic. ::: ================================================ FILE: content/collections/pages/licensing.md ================================================ --- title: Licensing intro: 'Statamic is available in two distinct flavors, but one splendid codebase. Statamic Core is **free and open source** and can be used for any type of project, while **Statamic Pro** is powerful commercial software designed for team and more robust use.' blueprint: page id: 56fadb93-b846-4867-ad73-4f721cc940c2 --- ## Core vs. Pro Statamic Core carries a few limitations you'll need to upgrade to Pro to remove: - One admin [user account](/users) - One [content form](/forms) Additionally, **Statamic Pro** also includes the following, exclusive features: - [Roles & Permissions](/control-panel/users#permissions) - [Revisions, Drafts, and Content History](/revisions) - Headless mode via [REST API](/rest-api) and [GraphQL](/graphql) - [Multi-site, multilingual, and multi-user-editing](/multi-site) - [White Label customization](/white-labeling) - [Git integration and automation](/git-automation) - Developer Support :::tip You can use Statamic Pro for as long you'd like in development. We call this "Trial Mode". ::: ## Enabling Pro When you install Statamic you will be asked if you want to enable **Pro**. If you decide skip it to start with Core, you can still opt into Pro at any time by running `php please pro:enable` via command line or updating your editions config file. ``` php // config/statamic/editions.php 'pro' => true, ``` ## Using Pro in Production Once it’s time to launch your Pro site on a public domain, there are a few things you need to do: - [Create a Site](#sites) on statamic.com and enter the appropriate domain(s). - Purchase any required licenses (e.g. Statamic Pro and/or any paid addons) and attach them to your Site. - Add your Site's license key to your environment file (preferred solution) or Statamic system config. ::tabs ::tab env ```env STATAMIC_LICENSE_KEY=your-site-license-key ``` ::tab config ```php // config/statamic/system.php 'license_key' => 'your-site-license-key', ``` :: :::tip If you're using the free version of Statamic and you don't have any commercial addons installed, you don't need to create and link a site. But you can if you want! Being organized is a nice thing. ::: ## Sites In your [statamic.com account](https://statamic.com/account/sites), you can create "Sites" that are used to organize your licenses (and in the future may provide some other nice features). Each Site has one unique license key that any and all commercial products are attached to and validated through. No more juggling a fist full of keys like a bunch of quarters at the arcade. Each license entitles you to run one production installation. You will need to specify the domains you plan to use from the "Sites" area of your Statamic Account. Domains are treated as wildcards so you can use subdomains for locales, testing, and other purposes. If you attempt to use the site from a domain not listed in your Site settings, you will get a notification inside the Control Panel informing you thusly to make the necessary changes. You may change the domain associated with a license at any time on [statamic.com](https://statamic.com/account/sites). ### Sites API You can programmatically create sites using our [Sites API](/sites-api). This is most useful while using our [Platform subscription plan](https://statamic.com/pricing/platform). ## License validation If you want to know about the legal terms you can [read those here](https://statamic.com/license). The rest of this article covers the more _technical_ aspects of the call-home features, domain restrictions, and so forth. ### Phoning Home Statamic pings The Outpost (our validation web service) on a regular basis. The Outpost collects the license key, public domain info (domain name, IP address, etc), and PHP version so we can validate them against your account. This happens once per hour, and only when logged into the control panel. Changing your license key setting will trigger an immediate ping to The Outpost. Tampering with outgoing API call will cause Statamic to consider your license invalid. If that happens, you'll need to open a [support request][support] to reinstate your license. If you need to run Statamic in an environment without an internet connection, please [contact support](https://statamic.com/support). ### Public Domains When Statamic calls home we use a series of rules to determine if the domain it’s running on is considered “public”. If any of the following rules match, the domain is considered _not public_ (letting you stay in Trial Mode) - Is it a single segment? eg. `localhost` - Is it an IP address? - Does it use a port other than `80` or `443`? - Does it have a dev-related subdomain? `test.`, `testing.`, `sandbox.`, `local.`, `dev.`, `stage.`, `staging.`, or `statamic.` - Does it use a dev-related TLD? `.local`, `.localhost`, `.test`, `.invalid`, `.example`, or `.wip` ## Special Circumstances [Contact us][support] if you have one and we'll see what we can do. [support]: https://statamic.com/support ================================================ FILE: content/collections/pages/lifecycle.md ================================================ --- title: Lifecycle id: fd34ca35-57d1-4d28-97f9-ba6801656b39 --- ## Laravel boots Request comes in to the server, and is handled by Laravel. Laravel will spin through all service provider classes and run each one's `register` method. You should put any container bindings in here. ``` php public function register() { $this->app->bind(SomeInterface::class, function () { return new SomeClass; }) } ``` It'll then loop through them again, this time calling the `boot` method. Here's where you can run any bootstrapping logic. ``` php public function boot() { // } ``` ## Auth service provider boots As part of the boot process, Statamic will set up its permissions. If you'd like to do anything permission or auth related, (like adding custom [permissions](/extending/permissions)) you should wrap your provider code in a booted callback to ensure it happens _after_ Statamic has done its thing. ``` php public function boot() { $this->app->booted(function () { Permission::register(...); }); } ``` ## View loads If you're using any JavaScript in the Control Panel, you can pass configuration variables to it. You can do this in a service provider or a view composer. ``` php View::composer('statamic::layout', function ($view) { Statamic::provideToScript(['foo' => 'bar']); }); ``` ``` js Statamic.$config.get('foo'); // 'bar' ``` ## Vue boots If you've ever built a Vue application, you'll know that any global components need to be registered _before_ the root Vue instance is created. Statamic provides a `booting` callback for that. ``` js Statamic.booting(() => { Statamic.$components.register(...); }) ``` Once the Vue app has been created but _before_ it's mounted, you'll have a chance to configure it inside a `configuring` callback. This is where you'd register Vue plugins or add global properties — anything that needs to happen on the app instance prior to mounting. ``` js Statamic.configuring(() => { Statamic.$app.use(SomePlugin); Object.assign(Statamic.$app.config.globalProperties, { $something: something, }); }); ``` Then, the Vue app will mount and you'll have a chance to do other JavaScript work within a `booted` callback. This is almost equivalent to putting things in a `created` hook of a Vue component. This is where you'd do things like adding [Bard extensions](/fieldtypes/bard#extending-bard) and wiring up [Hooks](/backend-apis/hooks) or [events](/vue-components/js-events). ``` js Statamic.booted(() => { Statamic.$bard.extend(...); Statamic.$hooks.on(...); }); ``` :::tip The Vue part of the lifecycle only applies to Control Panel requests. Since you have 100% control over the front-end of your site, you can do whatever you want there. ::: ================================================ FILE: content/collections/pages/linode.md ================================================ --- id: f8bac6fc-401c-4f0e-b338-386e332c91b8 blueprint: page title: 'How to Install Statamic on Linode' breadcrumb_title: Linode parent: ab08f409-8bbe-4ede-b421-d05777d292f7 intro: A full walk-through for installing, configuring, and running Statamic on a Linode Ubuntu virtual private server. --- ## Prerequisites There is only one prerequisite for this guide. You must have: - A [Linode](https://www.linode.com/) account (Use this referral code for ) - A Linode account ([this signup link](https://linode.gvw92c.net/9WzZX5) will give you $100 in free credit) ## Server Setup Follow the [official “Getting Started with Linode”](https://www.linode.com/docs/guides/getting-started/) guide to setup your server. Be sure to choose the Ubuntu image when creating your server — preferably the Ubuntu version 20.04 LTS. You can still use Ubuntu 18.04 or 16.04 if you want. To use old software. We recommend at least 1GB of memory on your VPS. ## Secure Your Server Follow the [official “How to Secure Your Server” Linode guide](https://www.linode.com/docs/guides/securing-your-server/) to secure your server. This guide covers web users, SSH hardening, trimming down network-facing services, configuring a firewall, and a lot of other stuff. ## Install Statamic on Ubuntu Now that you have a secure server with Ubuntu, follow our [Ubuntu instructions](/installing/ubuntu) to install Statamic. ================================================ FILE: content/collections/pages/live-preview.md ================================================ --- title: 'Live Preview' intro: 'Live Preview gives you the ability to see what your entry will look like in real time as you write and edit. You can configure and switch the preview screen size or pop it out into a new window.' template: page blueprint: page id: cdffd2c9-cf42-495d-a8f1-f416ddfddc29 --- ## Overview The ability to preview what your content looks like in real-time is practically a super power. You can be confident you won't have any layout surprises. Live Preview will render your work-in-progress content with whichever template you have currently loaded. You can even switch between templates while previewing. :::tip Keep in mind: Live Preview does not work using the `array` cache driver. :::
    Statamic Live Preview Statamic Live Preview
    And he's still touring, ladies and gentlemen.
    ## Device sizes You can customize the list of device sizes in `config/statamic/live_preview.php`. ``` php 'devices' => [ 'Laptop' => ['width' => 1440, 'height' => 900], 'Tablet' => ['width' => 1024, 'height' => 786], 'Mobile' => ['width' => 375, 'height' => 812], ], ```
    Device Size Switcher Device Size Switcher
    This dropdown will obey you better than any puppy will, guaranteed.
    ## Customizing the toolbar You may add extra input fields to Live Preview's header toolbar using custom Vue components. The values of these fields will be available in the data injected into the template. Define these inputs using an array of key/value pairs of field handles and vue component names in `config/statamic/live_preview.php`: ``` php 'inputs' => [ 'show_ads' => 'live-preview-ads', ] ``` Similar to fieldtypes, this component will be given a value prop and expect any changes to emit an input event. ``` javascript Statamic.$components.register('live-preview-ads', require('./LivePreviewAds.vue')); ``` ``` vue ``` These values are available in your views, scoped into the `live_preview` array: ``` {{ if live_preview:show_ads }}
    ...
    {{ /if }} ``` ## Preview targets On a Collection, you may define one or more preview targets which lets you choose which page should be viewed in the Live Preview window. For example, you may want to preview how an entry looks on the entry's page itself, as well as a listing page. ```yaml # content/collections/blog.yaml preview_targets: - label: Entry url: /blog/{slug} - label: Index url: /blog ``` You may use the entry's variables in the URL, just like defining a route. If you don't define any targets, it will use the entry's URL. ### Auto-refreshing :::tip If you're using Statamic in a headless environment, please refer to the [Auto-refreshing](#auto-refreshing-1) section below. ::: When the `refresh` option is enabled, a full refresh will occur whenever a change is made. When its disabled, Statamic will attempt to update the iframe's HTML automatically. If you're using Alpine or Livewire, its morphing function will be used. If you need to override how the iframe is updated, you may define a `StatamicLivePreviewMorph` closure: ```js window.StatamicLivePreviewMorph = (from, to) => { // Whatever you need to do... }; ``` You may opt-out of this behavior if you wish: ```php // config/statamic/live_preview.php 'hot_reload_contents' => true, ``` ## Headless / front-end frameworks To use Live Preview with a front-end framework, you may use a [preview target](#preview-targets) that points to a custom URL. For example, [Nuxt.js's Preview Mode](https://nuxtjs.org/docs/features/live-preview#preview-mode) requires that you point to a URL with a `preview=true` query parameter. ```yaml preview_targets: - label: Entry url: https://your-nuxt-app.com/blog/{slug}?preview=true ``` A `token` query parameter will be appended to the URL automatically, which you can then pass back to Statamic in a GraphQL query, where it will know to replace the entry with the Live Preview version. ```js $http.post('/graphql?token='+params.token, ...); ``` ### Auto-refreshing On a preview target, you may disable the behavior that causes a full refresh of the iframe when you make changes. By disabling the refresh, [postMessage](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) will be used instead to send a payload to the front-end. You can listen for this event and perform your own live updating behavior. First, set the preview target refresh setting to `false`: ```yaml preview_targets: - label: Entry url: https://your-nuxt-app.com/blog/{slug}?preview=true refresh: false ``` Then, the `event.data` will be an object containing the event name, reference of what you're editing, and the Live Preview token. ```js window.onmessage = function (e) { if (e.data.name === 'statamic.preview.updated') { updatePage(e) } } // A silly example where you update parts of the page by // querying the REST API with the provided token. const updatePage = function (e) { const id = e.data.reference.split('::')[1]; const url = `https://site.com/api/collections/articles/entries/${id}?token=${e.data.token}`; fetch(url) .then(response => response.json()) .then(response => { document.querySelector('#title').innerText = response.data.title; document.querySelector('#excerpt').innerText = response.data.excerpt; }); } // A more realistic example, using a front-end framework like Nuxt. const updatePage = function (e) { window.$nuxt.refresh(); } ``` ## Custom rendering If you need even more control, you may create your own route that retrieves the Live Preview entry through the token manually. Whatever you return from the route will be displayed within Live Preview. ```yaml preview_targets: - label: Entry url: /render-live-preview ``` ```php use Facades\Statamic\CP\LivePreview; use Illuminate\Http\Request; Route::get('/render-live-preview', function (Request $request) { $entry = LivePreview::item($request->statamicToken()); $entry->title; // The edited title $entry->foo; // The edited foo field, etc. $entry->live_preview; // All the "extra" data from the custom toolbar fields are in here. return view('whatever', ['entry' => $entry]); }); ``` ================================================ FILE: content/collections/pages/local.md ================================================ --- id: 2093f557-8d4a-4baf-bf5c-cbbf584acd3b blueprint: page title: 'How to Install Statamic Locally' nav_title: Local intro: 'Fast-track local install for getting Statamic running on your computer or development machine.' parent: ab08f409-8bbe-4ede-b421-d05777d292f7 --- ## Overview Running Statamic locally is the preferred method for building and maintaining your sites. With version control (we recommend git), it's quite simple to deploy changes almost instantly from your local computer to a live site with a single command. :::watch https://youtube.com/embed/zuKZQNUYSf8 Feel free to watch instead of read! ::: :::tip Heads up! This video assumes you're serving your local sites using [Laravel Valet][valet]. ::: ## Prerequisites To install Statamic locally you will need the following: - A computer running MacOS, Windows, or Linux - A supported version of [PHP](https://php.net) (we recommend PHP 8) - [Composer](https://getcomposer.org) to manage PHP packages ## Install Statamic CLI Statamic CLI is a commandline tool to help you get Statamic installed quickly and easily. The package can be installed on your machine using Composer: ``` shell composer global require statamic/cli ``` Once installed, you can run the command `statamic list` to see a list of available commands. :::tip If you run into any issues or errors, check out this [helpful article](/troubleshooting/fixing-issues-with-global-composer-packages) on what to do next. ::: ## Install Statamic In your terminal, `cd` to the parent directory you want to start a new Statamic project in and run the install command. ``` shell statamic new project_name ``` You'll be asked if you want to install a blank site or a [Starter Kit](/starter-kits). If this is your first time, we usually recommend starting with a blank site. Keep it simple. Next, you'll be prompted to set up your first super admin user. Do it. _After that_, everything is finished! ## Accessing the site The address where you access the site will be different depending on your development environment. For example, if you're using [Valet][valet] then your site would be at `http://$project_name.test` and the Control Panel at `/cp`. If you don't have Valet or some other server set up, you can run `php artisan serve` to use the built-in server, then use the URL it provides, (which is typically `http://127.0.0.1:8000`). ```cli $ php artisan serve Starting Laravel development server: http://127.0.0.1:8000 ``` ## Troubleshooting If your local environment is reasonably "up to date", everything should have gone smoothly. But let's face it, tech doesn't always work the way it's supposed to on the [first try](https://www.youtube.com/watch?v=3KDnrGdpNZY). Check out the [troubleshooting section](/troubleshooting) to get help about common error messages. ## What's Next You're now (probably) running the latest and greatest version of Statamic! Well done! 🎉 You can now get on with the fun parts. The [Quick Start Guide](/quick-start-guide) is a great place to head next if you're just kicking the tires (or tyres — if you're not from our neck of the woods). :::tip You can use Pro features while in development (like users, permissions revisions, REST API, and so on), by setting `'pro' => true` in `config/statamic/editions.php`. ::: :::tip Another Hot Tip The default install and all first-party Starter Kits use [TailwindCSS](https://tailwindcss.com/docs/just-in-time-mode) in Just In Time mode, so anytime you change classes in your HTML you'll need to recompile your CSS. This is super easy and happens automatically when you run `npm run dev` from the terminal in your project directory (as long as you've run `npm install` first). ::: [valet]: https://laravel.com/docs/valet ================================================ FILE: content/collections/pages/markdown.md ================================================ --- title: Customizing Markdown id: be292d2b-dc0e-48dc-bce4-0058df27ccc6 --- ## Basic Usage You may parse Markdown in Statamic by using the `Markdown` facade. ``` php Statamic\Facades\Markdown::parse('# Hello World!'); ``` ```html

    Hello World!

    ``` By default, Statamic follows the [CommonMark spec](https://spec.commonmark.org/current/) with a few extra features: - GFM Tables - HTML Attributes (eg. `# heading {.someclass #someid}`) - Strikethrough (eg. `~~strikethrough~~`) - Description Lists - Footnotes - Task Lists A few other extensions are available, but disabled by default: - Autolinking - HTML escaping - Automatic line breaks - Heading Permalinks - Table of Contents ## Customizing Markdown behavior Under the hood, we're using the [league/commonmark](https://commonmark.thephpleague.com/) package which supports all sorts of customization using extensions. ### Configuration You may customize the behavior of the markdown parser by providing a CommonMark config array in `config/statamic/markdown.php`. All the available options are outlined in the CommonMark docs. You can override the [base options](https://commonmark.thephpleague.com/2.4/configuration/) as well as any [extension](https://commonmark.thephpleague.com/2.4/extensions/overview/)'s options. You only need to provide the specific options you want to override. For example: ```php 'configs' => [ 'default' => [ 'allow_unsafe_links' => false, // [tl! ++:start] 'heading_permalink' => [ 'symbol' => '#', ], // [tl! ++:end] ] ] ``` ### Adding extensions You may add an extension with the `addExtension` or `addExtensions` methods. For example, in the `boot` method of your `AppServiceProvider`, return an extension instance, or an array of them. ``` php http://example.com`) | | `withSmartPunctuation()` | Convert plain quotes, dashes, ellipsis, etc into their unicode equivalents. Commonly referred to as "smartypants". (eg. `"CommonMark is the PHP League's Markdown parser"` becomes `“CommonMark is the PHP League’s Markdown parser”`) | | `withMarkupEscaping()` | Converts HTML to entities. Useful for securing input from untrusted users. (eg. `
    ` becomes `<div/>`) | | `withAutoLineBreaks()` | Converts newlines into `
    ` tags. Without this, you need to end a line with a `\` or two spaces. | | `withStatamicDefaults()` | Enable the default set of extensions that Statamic uses (tables, strikethrough, etc). Without this, you will get a plain parser. | | `newInstance($config)` | Gives you a new parser instance using an existing one as a starting point. It will inherit all the extensions and config. Accepts an array that will be merged into the config. | ## Custom Parsers Any methods on the `Markdown` facade are forwarded onto the `default` Parser. This includes the `addExtension` methods described above. You are free to create additional parsers that are configured independently, with their own configuration and extensions. ``` php Markdown::makeParser($config) // Accepts an optional league/commonmark config array. ->withAutoLinks() ->addExtensions(...) ->parse('# Heading'); ``` If you intend to reuse a parser, you may prefer to create it in one place (like a service provider), and then reference it elsewhere. ``` php Markdown::extend('special', function ($parser) { return $parser ->withStatamicDefaults() ->addExtensions(...); }); ``` ``` php Markdown::parser('special')->parse('# Heading'); ``` The closure provides you with a fresh `Parser` instance which you can customize as needed. :::tip If you need to provide a config to your custom parser, you can either [define it in the config file](#configuration) or pass an array as the second option. ```php Markdown::extend('special', $config, function ($parser) { // }); ``` ::: ### Extending the Parser class If you need more control than configuration and extensions allow – for example, manipulating the raw Markdown string before it hits the parser, or adding your own helper methods – you can extend the `Statamic\Markdown\Parser` class directly. ```php new CustomParser); ``` Because the underlying `newInstance()` method returns `new static`, chained helpers like `withAutoLinks()`, `withStatamicDefaults()`, and `newInstance()` will return instances of your subclass – so you keep access to your custom methods even after chaining. ### Using a custom parser in a modifier The `markdown` modifier accepts an optional argument to choose which parser to use. ::tabs ::tab antlers ```antlers {{ text | markdown:special }} ``` ::tab blade ```blade {!! Statamic::modify($text)->markdown('special') !!} ``` :: ### Using a custom parser in a fieldtype The `markdown` fieldtype allows you define the `parser`. This will be used when it augments the value, so you don't need the markdown modifier. ``` yaml - handle: content field: type: markdown parser: special ``` ================================================ FILE: content/collections/pages/modifiers.1.md ================================================ --- id: 998ad905-6988-457f-b26a-78bc64de6d3f title: Modifiers blueprint: link redirect: url: '@child' status: 301 --- ================================================ FILE: content/collections/pages/modifiers.md ================================================ --- title: Building Modifiers template: page updated_by: 42bb2659-2277-44da-a5ea-2f1eed146402 updated_at: 1569264085 intro: Modifiers give you the ability to manipulate the data of your variables on the fly. They can manipulate strings, filter arrays and lists, help you compare things, do basic math, simplify your markup, play Numberwang, and even help you debug. id: e052ecb8-60d9-4afa-980e-ce128c301d70 --- ## Anatomy of a Modifier A modifier consists of a few parts. Let’s break it down. ::tabs ::tab antlers ```antlers {{ variable | repeat:2 }} ``` - The first part? A regular old variable: `variable`. - Next up, the modifier's [handle](#handle): `repeat` - And finally, a parameter: `2` ::tab blade ```blade {{ Statamic::modify($variable)->repeat(2) }} ``` - First, we call `Statamic::modify`, supplying the variable name we want to modify (`$variable`) - Next up, we call a method with the modifier's [handle](#handle) (`->repeat()`) - If our modifier accepts parameters, we supply them to the modifier's method call (`->repeat(2)`) :: Parameters are used to modify the behavior of a modifier. They could be anything from an integer or boolean to a variable reference. It’s up to you. ## Creating a Modifier You can generate a Modifier class with a console command: ``` shell php please make:modifier Repeat ``` This'll create a class in `app/Modifiers` which will be automatically registered. To create and register a modifier inside an addon instead, check out the [addon docs](/extending/addons#registering-components). ## Modifying a value The modifier class expects one `index` method which should return a modified `$value`. ``` php repeat() }} ``` :: The first and only required argument passed into `index` will be the `$value` that needs modifying. We can do anything to this value as long as we return it when we’re done. Once returned, the template will either render it, or pass it along the next modifier in the chain. The other two arguments are optional: - `$params` will be an array of any parameters. - `$context` will be an array of contextual data available at that position in the template. ## Handle The modifier's handle is how it will be referenced in templates. By default, this will be the snake_cased version of the class name. In this example above, it would be `repeat`. You can override this by setting a static `$handle` property. ``` php protected static $handle = 'repeatrepeat'; ``` ::tabs ::tab antlers Then, using the example above, `variable | repeat` would now be `variable | repeatrepeat`. ::tab blade Then, using the example above, `Statamic::modify($variable)->repeat()` would now be `Statamic::modify($variable)->repeatrepeat()`. :: ## Aliases You may choose to create aliases for your modifier too. It will then be usable by its handle, or any of its aliases. ``` php protected static $aliases = ['duplicate']; ``` ## Example Let’s say we need a modifier that repeats things. Maybe even delicious things. ``` php repeat() }} {{ Statamic::modify($thing)->repeat(3) }} {{ Statamic::modify($thing)->repeat($times) }} ``` :: You would find yourself with varying amounts of pizza. ```html PizzaPizza PizzaPizzaPizza PizzaPizzaPizzaPizzaPizza ``` ================================================ FILE: content/collections/pages/multi-site.md ================================================ --- title: Multi-Site intro: | Statamic's multi-site capabilities are designed to manage variations of a **single site**, and/or different sections of a single site running on one or more domains or subdomains. It can be used to manage translations, country-specific versions of a company site, put an area such as `support` or `resources` on a subdomain, and other similar use cases. _It is not intended to be used for multi-tenant applications_ running completely separate sites. template: page id: fb20f2e0-3881-43e6-8507-3308a18c54b0 blueprint: page pro: true --- ## Overview Statamic can be configured to handle multiple "sites". A site is a way of managing a localized version of your content - whether another language, region, or even company/brand (think Proctor & Gamble). Each site can have different base URLs: - domains: `hello.com` and `bonjour.com` - subdomains: `example.com` and `fr.example.com` - subdirectories: `example.com` and `example.com/fr/` If you're looking to run many independent websites from a shared codebase, multi-site is not the right tool. We are intentionally opinionated here, and you should explore our [Platform Pricing](https://statamic.com/pricing/platform) model ::: tip Every Statamic install needs at least one site. Building zero sites is a bad way to build a website and clients will probably challenge your invoices. ::: ### Converting existing content to multi-site The simplest way to convert existing content to a multi-site friendly structure is to run the automated command: ``` shell php please multisite ``` Read more on [converting to a multi-site setup](/tips/converting-from-single-to-multi-site). ## Configuration ### Enabling multi-site First, enable `multisite` in your `config/statamic/system.php`: ``` php 'multisite' => true, ``` ### Adding new sites Next, you can add new sites through the control panel by clicking the Site menu item in the sidebar:
    Configure sites page in control panel Configure sites page in control panel
    Or directly in your `resources/sites.yaml` file: ``` yaml default: name: First Site url: / locale: en_US second: name: Second Site url: /second/ locale: en_US ``` ## Available options Let's look at a full site configuration, and then we'll explore all of its options. ``` yaml # resources/sites.yaml en: name: English url: / locale: en_US lang: en attributes: theme: standard ``` ### Handle Each site is keyed by its `handle`, which is important for directory structure, as well as referencing sites in collection configs, etc. throughout your site. Changing this is non-trivial, and you should be careful if you already have established content in this site. Read more about [renaming sites](#renaming-sites). ``` yaml en: # <- This is your site handle name: English ``` ### Name Each site has a `name`, which is a display-friendly representation of your site's name mostly seen within control panel UI. Changing this does not affect content relations. ``` yaml en: name: English ``` ::: tip You'll notice the default site dynamically references a [config variable](/variables/config), but feel free to change this! ``` yaml default: name: '{{ config:app:name }}' ``` ::: ### URL Each site requires a URL to define the root domain Statamic will serve and generate all URLs relative to. The default `url` is `/`, which is portable and works fine in most typical sites. Statamic uses a little magic to work out what a full URL is based on the domain the site is running on. :::best-practice It can be a good idea to change this to a **fully qualified, absolute URL**. This ensures that server/environment configurations or external quirks don't interfere with that "magic". ```yaml en: # ... url: '{{ config:app:url }}' fr: # ... url: '{{ config:app:url }}/fr/' ``` By default, this is linked to your `APP_URL` environment variable, which allows you to control the exact URL by environment: ```env # production APP_URL=https://mysite.com # development APP_URL=http://mysite.test ``` ::: ### Locale Each site has a `locale` used to format region-specific data (like date strings, number formats, etc). This should correspond to the server's locale. By default, Statamic will fallback to your app's locale. ### Language Statamic's control panel has been translated into more than a dozen languages. The language translations files live in `resources/lang`. You may specify which language translation to be used for each site with the `lang` setting. If you leave it off, it'll use the short version of the `locale`. e.g. If the locale is `en_US`, the lang will be `en`. ``` yaml de: name: Deutsch locale: de_DE # Lang not needed, as `de` is implied de_CH: name: 'Deutsch (Switzerland)' locale: de_CH lang: de_CH # We want the `de_CH` language, not `de` ``` Note that both Statamic and Laravel don't ship with frontend language translations out of the box. You have to provide your own string files for this. There is a great package called [Laravel Lang](https://github.com/Laravel-Lang/lang) containing over 75 languages that can help you out with this. ### Additional attributes You may also include additional arbitrary `attributes` in your site's config, which can later be accessed with the [site variable](/variables/site). ``` yaml en: # ... attributes: theme: standard ``` ::tabs ::tab antlers ```antlers ``` ::tab blade ```blade ``` :: In Blade, you can also use the `attribute()` method, which supports dot notation and a fallback value for attributes that may not be set: ```blade ``` :::tip Nothing fancy happens here, the values are passed along "as is" to your templates. If you need them to be editable, or store more complex data, you could use [Globals](/globals). ::: ## Text direction Text direction is automatically inferred by Statamic, based on the [language](#language) of your configured site. For example, most sites will be `ltr`, but Statamic will automatically use `rtl` for languages like Arabic or Hebrew. If you need to reference text direction in your front end, you can make use the [site variable](/variables/site): ::tabs ::tab antlers ```antlers ``` ::tab blade ```blade ``` :: ## Renaming sites If you rename a site's [handle](#handle), you'll need to update a few folders and config settings along with it. Replace `{old_handle}` with the new handle in these locations: **Content folders** - `content/collections/{old_handle}/` - `content/globals/{old_handle}/` - `content/trees/{old_handle}/` **Collection Config YAML Files** ``` yaml # content/collections/{collection}.yaml sites: - {old_handle} - de - fr ``` ## Permissions Within the Control Panel, you will not be able to access items in a particular site if you do not have permission. You may grant permission for any of your sites by adding an `access {site_handle} site` to the appropriate role. For example: ```yaml permissions: - edit blog entries - access english site # [tl!++] - access french site # [tl!++] ``` [Read more about permissions](/users#permissions) ## Per-Site Views {#views} [Views](/views) can be organized into site directories. If a requested view exists in a subdirectory with the same name as your site [handle](#handle), it will load it instead. This allows you to have site-specific views without any extra configuration. ``` yaml # resources/sites.yaml site_one: # ... site_two: # ... ``` ``` files theme:serendipity-light resources/views/ site_one/ home.antlers.html home.antlers.html page.antlers.html ``` For example, given `template: home`, Statamic will load `site_one/home` because that view exists in the subdirectory. If you were to have `template: page`, it would load the one in the root directory because there's no site-specific variant. ## Template snippets Here are a few common features you'll likely need to template while building a multi-site. ### Building a site switcher {#site-switcher} This will loop through your sites and indicate the current site as the active one. Check out all the [available variables inside the `sites` loop](/variables/sites). ::tabs ::tab antlers ```antlers {{ sites }} {{ handle }} {{ /sites }} ``` ::tab blade ```blade @foreach ($sites as $switcherSite) $switcherSite->handle == $site->handle ]) href="{{ $switcherSite->url }}" >{{ $switcherSite->handle }} @endforeach ``` :: ### Declaring the page language Indicate the current language of the site by setting the `lang` attribute on your `` tag (most likely in your layout view), or the container element around translated content if the page mixes and matches languages. ::tabs ::tab antlers ```antlers ``` ::tab blade ```blade ``` :: ## Static caching If your multi-site should use static caching, you will also need to add additional config parameters and different server rewrite rules. Please refer to the related section of the [static caching documentation](/static-caching#multisite) for the correct settings. ## Enabling fields By default, your existing fields won't be enabled for multi-site editing. To enable a field to be editable within another site, navigate to the field and click the globe icon (Localizable). ================================================ FILE: content/collections/pages/multi-user-collaboration.md ================================================ --- title: 'Multi-User Collaboration' intro: 'Stop worrying if someone else is editing the same article at the same time and start enjoying a collaborative authoring process. Each field automatically locks as a user begins to edit, and unlocks when they leave or go idle.' template: page blueprint: page pro: true id: a3adf32a-37a5-4e96-beee-f107dc1b81a9 --- ## Overview **Features include:** - Presence indicators when multiple users are editing the same entry - Field locking when another user begins editing - Content changes are updated in real-time for all users
    Statamic's multi-user collaboration in action
    A glimpse of multi-user collaboration in action.
    ## Installation Follow the docs on the [Collaboration addon package](https://statamic.com/addons/statamic/collaboration) to get started! ================================================ FILE: content/collections/pages/navigation.md ================================================ --- id: 2af9fc45-66d0-4ca5-9761-00017076144f blueprint: page title: Navigation intro: 'A nav (or navigation for long) is a hierarchy of links and text nodes that are used to build navs and menus on the frontend of your site. Trust me, you''ve seen them before. You''re looking at one right now, just move your eyeballs up a little bit. Yeah, there it is.' related_entries: - ed746608-87f9-448f-bf57-051da132fef7 - 485f1703-fc6f-4d0f-94f2-e84ae625e1b7 - 3c34ef5c-781e-4a22-a09b-25f58bdb58a8 - 35c9cd07-f377-4fcb-b02c-72c1925e6fdf --- ## Overview Each Nav is a [structure](/structures) giving you the ability to rearrange items through the delightful experience of dragging and dropping boxes.
    A Statamic structure page tree A Statamic structure page tree
    - You can **reference** entries, enter hardcoded URLs (internal or external), or enter simple text blocks (which can be used as section headers for dropdown navs, for example). - You can **choose** which collections' entries will be available to choose from. - Any referenced entries will use the URLs **defined by the collection**, regardless of the position in the Structure. - You can place the same entry **multiple** times. Two times, three times, four times, even six times are all possible numbers of times you can place something. :::watch https://www.youtube.com/embed/POgIsLeWGGQ Watch how to build a simple nav with a Structured Collection ::: ## Storage Navs are stored in `content/navigation`. Each gets its own YAML file whose handle matches its filename. The actual contents of the structure - the "tree" - is stored separately in `content/trees/navigation`. ``` files theme:serendipity-light content/ navigation/ header.yaml footer.yaml trees/ header.yaml footer.yaml ``` ``` yaml # content/navigation/footer.yaml title: Footer max_depth: 3 collections: - pages - posts - documents ``` ## Templating You can work with the [nav](/tags/nav) to loop through and render your HTML with access to all the entries and nodes in the navigation. ::tabs ::tab antlers ```antlers ``` ::tab blade ```blade
  • {{ $title }}
  • ``` :: Within the tag pair, you will have access to any fields defined on that particular nav item - the item itself or the entry. See [blueprints and data](#blueprints-and-data) below for more information. ## Collections Your navigation tree _may_ contain references to entries. The control panel's entry selector will show you entries across all collections by default. You may narrow down which collections will appear in the selector in the config area.
    Configuring navigation collections Configuring navigation collections
    If you want to put pants in your navs, you can.
    ## Blueprints and data Out of the box, nav items are fairly light. If you create a standard nav item, you can type in the URL and title. For entry reference nav items, you can override the title. If you'd like to add more data, you can add fields to the nav's blueprint. Any fields you add will appear in the editor pane in the control panel.
    Navigation Page Editor
    The excerpt and icon fields have been added
    The data will be saved in a `data` key on the tree branch. ``` yaml - title: My page url: /my-page data: excerpt: This is my page icon: page.svg ``` In the case of entry reference nav items, any fields you add to the nav blueprint will override the fields for that entry. This is useful if you intentionally want to override an entry's value. If you want to do this, make sure that you use the same fieldtype as what's in the entry's blueprint. A good way to handle that is to make a [reusable field](/blueprints#reusable-fields). ## Localization When running a [multi-site](/multi-site) installation, you can have a different tree for each nav. Learn more about [localizing navs](/tips/localizing-navigation). ================================================ FILE: content/collections/pages/netlify.md ================================================ --- id: 7f809a5e-e555-4ccc-8488-d7310ff8c89c blueprint: page title: 'Deploying Statamic to Netlify' intro: |- Netlify is a Jamstack service and cloud provider that lets you deploy your Statamic site statically with blazing fast performance using its Edge CDN. parent: c4f17d05-78bd-41bf-8e06-8dd52f6ec154 --- :::warning Please note that by hosting your site statically with a service like Netlify or Vercel, **you can't access the Control Panel in production** and are **not able to use dynamic features** like Statamic's built-in forms or random sorting in your templates. ::: Deployments are triggered by committing changes to your Git repository. Alternatively, you can also upload the locally generated files from Netlify's dashboard via drag and drop. ## Prerequisites :::tip While Netlify supports PHP versions from 7.4 through 8.3, it defaults to PHP 8.0. You can [specify the PHP version](https://docs.netlify.com/configure-builds/manage-dependencies/#php) using the `PHP_VERSION` environment variable. ::: - A [Netlify](https://netlify.com) account - An account with one of Netlify's supported Git providers - Have the `statamic/ssg` package installed and set up in your project - Make sure you don't need or use any dynamic features, like forms or random sorting. - To make things easier, add a new script to your project's `composer.json` file with the following commands: ```json "scripts": { "build": [ "npm run build", "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"", "@php artisan key:generate", "@php please ssg:generate" ], // ... } ``` Feel free to customize this to fit your needs. Netlify automatically installs npm and composer dependencies. So there is no need to manually run `npm ci` or `composer install`. ## Creating a New Site To get started add a new site to your account. In this walk-through we'll assume you already have an existing project that you want to import since it's the most common use-case.
    Netlify dashboard to import a project
    Next, you will need to authorize Github (or another supported source control provider). This is a one-time process that allows you to quickly deploy new sites from this account.
    Choose a Git provider for your project
    After you connected to your Git provider, pick the repo of the project you want to deploy to Netlify. So choose wisely 🧙‍♂️
    Pick a repository from your chosen Git provider
    The next step is to configure your site's settings and deployment configuration. 1. Choose the branch you want to deploy from. This could be the `main` branch or something like `production`, depending on your [Git workflow](/deploying/workflow). 2. Leave the base directory input empty, unless your Statamic project is part of a monorepo or lives in a subfolder. 3. Use `composer build` for the build command if you set up your `composer.json` like mentioned [above](#prerequisites). - Otherwise just use `php please ssg:generate` 4. Set the publish directory to `storage/app/static` 5. Add `APP_KEY` env variable, by running `php artisan key:generate` locally, and copying from your `.env` - ie. `APP_KEY` `your-app-key-value` 6. Add `APP_URL` environment variable after your site has a configured domain - ie. `APP_URL` `https://thats-numberwang-47392.netlify.com` Instead of doing all of this manually, you can also use a `netlify.toml` file at the base of your project. Netlify automatically detects the file and sets everything up for you. Feel free to use [this one](https://gist.github.com/joshuablum/845d6af82c710a9b9d8f1a57618f213d) as a reference or starting point. [More about file-based configuration in Netlify's docs](https://docs.netlify.com/configure-builds/file-based-configuration/).
    Site and deploy settings for the site
    Quite some options, don't get lost.
    That's it! ✅ Depending on how large and complex your project is, the **deployment might take a few seconds or minutes**. After this is done, you can visit your site via a URL provided by Netlify. It looks similar to this [https://thats-numberwang-47392.netlify.app](https://thats-numberwang-47392.netlify.app). Be sure to have a look at Netlify's docs on [custom domains](https://docs.netlify.com/domains-https/custom-domains/), [enabling HTTPS](https://docs.netlify.com/domains-https/https-ssl/), and how [Netlify forms](https://docs.netlify.com/forms/setup/) work. ## SSG configuration for Netlify After you have installed the `statamic/ssg` package, you can publish its configuration with the following command: ```php php artisan vendor:publish --provider="Statamic\StaticSite\ServiceProvider" ``` This allows you to customize the behaviour of the package. Let's say you have additional folders and files that you need for your site. Just add them to the copy array: ```php 'copy' => [ public_path('css') => 'css', public_path('js') => 'js', public_path('assets') => 'assets', public_path('favicon.ico') => 'favicon.ico', ], ``` ## Storing Assets in S3 If you are storing your assets in an S3 bucket, the `.env`s used will need to be different to the defaults that come with Laravel, as they are reserved by Netlify. For example, you can amend them to the following: ```sh # .env AWS_S3_ACCESS_KEY_ID= AWS_S3_SECRET_ACCESS_KEY= AWS_S3_DEFAULT_REGION= AWS_S3_BUCKET= AWS_URL= ``` Be sure to also update these in your `s3` disk configuration: ```php // config/filesystems.php 's3' => [ 'driver' => 's3', 'key' => env('AWS_S3_ACCESS_KEY_ID'), 'secret' => env('AWS_S3_SECRET_ACCESS_KEY'), 'region' => env('AWS_S3_DEFAULT_REGION'), 'bucket' => env('AWS_S3_BUCKET'), 'url' => env('AWS_URL'), ], ``` ## Using SEO Pro By default, the SEO Pro addon generates the `sitemap.xml` and `humans.txt` files dynamically and on the fly. For both files to be part of your generated site, explicitly add their URLs to the array of the `Additional URLs` section in the configuration file: ```php # config/statamic/ssg.php 'urls' => [ '/sitemap.xml', '/humans.txt', ], ``` ================================================ FILE: content/collections/pages/oauth.md ================================================ --- id: 3dbb14fd-a762-4891-bce1-daf13b8c5981 blueprint: page title: OAuth template: page pro: true related_entries: - 6b691e04-8f28-4eb2-8288-b61433883fe4 --- ## Overview Statamic supports OAuth authentication via [Laravel Socialite](https://github.com/laravel/socialite), which includes support for Facebook, Twitter, Google, LinkedIn, GitHub, and Bitbucket. The [Socialite Providers][socialite-providers] Github organization contains over 100 additional pre-built providers that you can take advantage of as well. If you require a provider not on the list (perhaps you need a custom one for your own application), you may [create your own provider](#custom-providers). ## Installing Socialite Install Socialite with the following Composer command: ``` shell composer require laravel/socialite ``` Enable OAuth in `config/statamic/oauth.php` or in your environment file: ``` env STATAMIC_OAUTH_ENABLED=true ``` Add the provider to the [oauth config](#configuration). This will allow Statamic to add buttons to the CP login form. ``` php 'providers' => [ 'github', ], ``` Add your provider's credentials to `config/services.php` and [callback URL](#routes) as per the Socialite documentation: ``` php 'github' => [ 'client_id' => env('GITHUB_CLIENT_ID'), 'client_secret' => env('GITHUB_CLIENT_SECRET'), 'redirect' => 'http://your-site.com/oauth/github/callback', ], ``` If you plan to use a third party provider, follow the steps [below](#third-party-providers). ## Usage Send your users to the provider’s login URL to begin the OAuth workflow. Buttons for each configured provider will be available on the Control Panel's login page, but you may also do this on the front-end with the `oauth` tag: ``` Log in with Github ``` Once they've logged in at their provider's site, they will be redirected back to your site where a Statamic user account will either be retrieved or created. They will then be automatically logged into your site with the Statamic account. However, you may [customize the user flow](#user-flow). ## Configuration OAuth behavior may be configured in `config/statamic/oauth.php`. ### Providers You should add your intended OAuth providers to the config so Statamic can provide your users with buttons on the login page. You can specify just the name of the provider, or use a name/label pair if you would like to customize how it's displayed. ``` php 'providers' => [ 'facebook', 'github' => 'GitHub', 'twitter', ], ``` If a provider requires ["stateless authentication"](https://laravel.com/docs/socialite#stateless-authentication), you may pass an array and specify the `stateless` config option: ``` php 'providers' => [ 'saml2' => ['stateless' => true, 'label' => 'Okta'], ], ``` ### Routes There are 2 required routes in order for the OAuth workflow to function: - A login redirect route, which sends users to the provider's login page. - A callback route, which the provider will redirect to after a successful login. You may customize these in `config/statamic/oauth.php`: ``` php 'routes' => [ 'login' => 'oauth/{provider}', 'callback' => 'oauth/{provider}/callback' ], ``` When you create your OAuth application, you will need to provide the callback URL. ### User flow By default, once a user has logged in at their provider's site, they will be redirected back to your site where a Statamic user account will either be retrieved or created. They will then be automatically logged into your site with the Statamic account. Additionally, any user data from the provider will be merged into that user's account. You may choose to customize this flow. ```php 'create_user' => true, 'merge_user_data' => true, 'unauthorized_redirect' => null, ``` By setting `'create_user' => false`, if a corresponding Statamic user account doesn't exist, one will not be created for them, and they will be redirected to the unauthorized error page. ## Third party providers If you would like to use a provider not natively supported by Socialite, you should use the [SocialiteProviders][socialite-providers] method. 1. Require the appropriate provider using Composer: ``` composer require socialiteproviders/dropbox ``` 1. Next, add an event listener in your `AppServiceProvider`'s `boot` method: ```php // app/Providers/AppServiceProvider.php Event::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) { $event->extendSocialite('dropbox', \SocialiteProviders\Dropbox\Provider::class); }); ``` Alternatively, if your application has an `EventServiceProvider.php` file, you can register the event listener in there: ```php protected $listen = [ \SocialiteProviders\Manager\SocialiteWasCalled::class => [ 'SocialiteProviders\\Dropbox\\DropboxExtendSocialite@handle', ], ]; ``` 3. Add the service credentials to `config/services.php` config: ``` php 'dropbox' => [ 'client_id' => env('DROPBOX_CLIENT_ID'), 'client_secret' => env('DROPBOX_CLIENT_SECRET'), 'redirect' => 'http://your-site.com/oauth/dropbox/callback', ], ``` 4. Add the provider to the `config/statamic/oauth.php` config: ``` php 'providers' => [ 'dropbox', ], ``` ## Custom providers If your OAuth provider isn’t already available in Socialite or [SocialiteProviders][socialite-providers], you may create your own. To create your own OAuth provider, you should make your own SocialiteProvider-ready provider. All that's needed is the event handler (eg. `DropboxExtendSocialite.php`) and the provider (eg. `Dropbox.php`). Follow the [third party installation steps](#third-party-providers), but skip the Composer bits. You can just keep the classes somewhere in your project. ## Customizing user data After authenticating with the provider, Statamic will try to retrieve the corresponding user, or create one if it doesn't exist. You may customize how it's handled by adding a callback to your `AppServiceProvider`. ### User data The only data added to the user will be their `name`. If you would like to customize what gets added to the user, you can return an array from the provider's `withUserData` callback. The closure will be given: - an instance of `Laravel\Socialite\Contracts\User` - the existing `Statamic\Contracts\Auth\User` if one already exists. ``` php use Statamic\Facades\OAuth; OAuth::provider('github') ->withUserData(fn ($socialiteUser, $statamicUser) => [ 'name' => $socialiteUser->getName(), 'created_at' => optional($statamicUser)->created_at ?? now()->format('Y-m-d'), ]); ``` :::warning This user data will get merged into the user every time they log in using OAuth. This includes if they had an existing non-OAuth user account. ::: ### Customize entire user creation If you want more control over the actual user object being created, you can return a user from the provider's `withUser` callback. The closure will be given an instance of `Laravel\Socialite\Contracts\User`. ``` php use Statamic\Facades\User; use Statamic\Facades\OAuth; OAuth::provider('github')->withUser(function ($user) { return User::make() ->email($user->getEmail()) ->set('name', $user->getName()); }); ``` :::warning This will only be used when the user is initially created. If you'd like to also update the data on every login, you should combine this with the `withUserData` option above. ```php public function boot() { OAuth::provider('github') ->withUserData(fn ($user) => $this->userData($user)) ->withUser(function ($user) { return User::make() ->email($user->getEmail()) ->data($this->userData($user)); }); } private function userData($user) { return [ 'name' => $user->getName(), ]; } ``` ::: [socialite-providers]: https://socialiteproviders.com/ ================================================ FILE: content/collections/pages/overview.1.md ================================================ --- id: e0e93aba-4abc-4433-9257-3321a4521d60 blueprint: page title: 'Control Panel Overview' nav_title: Overview intro: > The Statamic Control Panel is where you manage everything that makes your site… well, your site. It's the admin interface you use to create and edit content, manage users and permissions, tweak settings, access utilities, and interact with addons — all without needing to touch the filesystem directly ---
    Statamic v6 Control Panel Statamic v6 Control Panel
    Behold — the Statamic Control Panel!
    The control panel is built to be fast, modern, and flexible — and it gets a lot smarter in v6 with things like a command palette and updated UI patterns designed for real editors and developers alike. Below is a quick explanation of the major areas you'll see once you're logged in. ## Dashboard The first screen you see after signing in is the [Dashboard](/dashboard) — a customizable area where you can add widgets for things you care about: recent entries, scheduled content, form submissions, updates, shortcuts, and more. Configure it once and it becomes your command center. ## Content Management This is where the magic happens. [Collections](/collections) organize your content types — blog posts, pages, products, whatever you need. Each collection can have multiple [blueprints](/blueprints) defining different field structures, and you can use [globals](/globals) for site-wide content that appears everywhere. The [Asset Browser](/assets) handles all your files — images, documents, videos — with built-in image editing, organization, and optimization tools. ## Users & Permissions The Users section lets you create, manage, and invite people who can log into the control panel. [Roles and permissions](/users) let you control what each user or role can see and do, from read-only editors to full administrators. ## Command Palette Hit `cmd+k` (or `ctrl+k` on Windows/Linux) and the Command Palette pops up — a quick way to navigate around the control panel, jump right into editing specific entries, and run actions without clicking through menus. It's like Spotlight for your CMS. ## Preferences Every user can adjust their own Preferences — like theme (light/dark), start page, locale, and more, while admins can also set defaults or role-based preferences. Make the control panel work the way you work. ## Forms & Submissions [Forms](/forms) let you collect data from your site visitors — contact forms, newsletter signups, surveys, whatever. All submissions are stored in the control panel where you can view, export, or process them however you need. ## Content Tools The control panel includes powerful tools that make content work easier: [Live Preview](/live-preview) — see your changes as you write, with real-time updates as you edit. [Git Automation](/git-automation) — manage how content updates sync with your Git workflow, keeping your content and code in sync. [Revisions](/revisions) — create versions of your content, track changes, and easily rollback to previous versions of your entries. [Utilities](/utilities) — standalone tools with their own screens and permissions, like the Cache Manager, PHP Info Viewer, and Email Config. [Multi-Site](/multi-site), [Translations](/cp-translations), [Conditional Fields](/control-panel/conditional-fields), [Elevated Sessions](/control-panel/elevated-sessions), [White Labeling](/control-panel/white-labeling), and more — all features that let you shape the CP experience to your needs. ## Navigation & Extensibility Statamic's control panel nav is not set in stone. You can [customize what editors see](/control-panel/customizing-the-cp-nav), hide sections they don't need, reorder items, and if you're building addons you can extend or add your own control panel navigation items. The control panel adapts to your workflow, not the other way around. ================================================ FILE: content/collections/pages/overview.10.md ================================================ --- id: 621873af-a669-42be-8bc3-5546a8c597e6 title: 'Starter Kits Overview' nav_title: Overview intro: |- Starter Kits are pre-built site packages that jump-start new Statamic sites with features, functionality, and even design. Built by the core team or designers & developers in the community, Starter Kits can cover a wide range of uses, from fully-built, ready-to-go sites, to developer-focused boilerplates for common frontend frameworks. Starter Kits can be shared and even sold on the Statamic Marketplace. blueprint: page --- ## Statamic starter kits vs WordPress themes While they may seem similar on the surface, Statamic starter kits and WordPress themes take a very different approach to the end-goal of speeding up the web design and development process. Allow us to explain the difference. Many WordPress themes are interchangeable because WordPress uses the same content model for all sites. This is both a strength and a weakness of the platform. The strength is the interchangeability, but the weakness is that not every business or website _should_ be the same. Sites often outgrow their themes with feature and content needs the theme doesn't support. When this happens, site owners need to either start hacking and slashing away at someone else's code, or installing plugins and hoping for the best. Statamic Starter Kits are designed to be a **starting place**. Good, clean code, ready to be changed, built-upon, and adapted to your needs. Each Starter Kit can have a unique content model, design, and set of features that fits its creator's purpose. We envision Starter Kits as a great way to "skip ahead" in the usual development cycle of a website, and less of a "no-code" approach to web development. ## Where to find starter kits The best way to find starter kits is by exploring the [Statamic Marketplace](https://statamic.com/marketplace).
    Podcaster – a Statamic Starter Kit
    This is Podcaster — a Starter Kit for podcasters.
    ## Recommended reading - [How to install a starter kit](/starter-kits/installing-a-starter-kit) - [How to update a starter kit](/starter-kits/updating-a-starter-kit) - [How to create your own starter kit](/starter-kits/creating-a-starter-kit) --- ================================================ FILE: content/collections/pages/overview.12.md ================================================ --- id: b80820bb-c2e8-475f-98bd-8ea0ef9f5339 blueprint: page title: 'Vue Components Overview' overview: "Here's how you can add your own Vue 3 components to the Statamic Control\_Panel." nav_title: Overview --- ## Registering Components In order to use a custom Vue component, it needs to be registered. You should do this inside the `Statamic.booting()` callback. Once registered, you (or Statamic) will be able to use the component. ``` vue ``` ``` js import MyComponent from './Components/MyComponent.vue'; Statamic.booting(() => { Statamic.$components.register('my-component', MyComponent); }); ``` ## Appending Components Registered components may also be appended to the end of the page at any point. ``` js const component = Statamic.$components.append('publish-confirmation', { props: { foo: 'bar' } }); ``` This will return an object _representing_ the component. On this object, you have access to a number of methods to interact with the Vue component. ### Updating props ``` js component.prop('prop-name', newValue); ``` ### Adding event listeners It works just like Vue's `$on` method: ``` js // inside component this.$emit('event-name', { foo: 'bar' }); ``` ``` js component.on('event-name', (payload) => { console.log(payload); // { foo: 'bar' } }); ``` ### Destroying the component ```js component.destroy(); ``` ================================================ FILE: content/collections/pages/overview.2.md ================================================ --- id: 84100772-18e4-4a22-8759-219b242a320c blueprint: page title: 'Frontend Overview' intro: "Frontend, backend, control panel, client-side, server-side, left-side, strong-side, front-side fakey 180...there's a lot of terminology flying around referring to the various aspects of a website. Let's clear 'em up, at least in the Statamic context." nav_title: Overview template: page --- ## Clarification The **frontend** of a website is the part users see and interact with in their browser. The .com bit. It's the text, images, videos, pages, layouts, RSS feeds, and other bits that your readers and visitors consume. :::tip It's likely this isn't new information – most people who read these docs are developers with front-end experience. Please keep reading though! There's good info in here. ::: When we refer to the frontend of a _Statamic_ site, we're talking about the templates and views, JavaScript/CSS files, media assets, and other resources used to render your final website. The **backend** of a Statamic site is all of the PHP and Laravel code that you _can_ customize and extend to bring your own unique features and capabilities to life on your site. Statamic's **Control Panel** sits _outside_ both the frontend and backend as a tool used to publish and manage content, users, and assets. ## The frontend is yours In today's tech-driven ecosystem there are countless ways to build a website. Some might say _too_ many. You could... - Write a Single Page Application (SPA) with [Vue.js](https://vuejs.org) or [React](https://reactjs.org) to run your entire site without the need for page refreshes - Use HTML and Statamic's [Antlers](/antlers) template language to build a dynamic site with smart caching - Use [Vite][vite], [Webpack](https://webpack.js.org), [Laravel Mix][mix], or [Gulp](https://gulpjs.com) to compile your JavaScript and SCSS/LESS - Go for the [JAMStack](https://jamstack.org) approach and run a statically generated site without server-side processing - Build a standard Statamic site and deploy a static version to [Netlify](https://www.netlify.com) - Go skateboarding and stay away from computers and nerdy webmasters - Kick it old-school and write your own HTML, plain CSS, and vanilla JavaScript Just like the [honey badger](https://www.youtube.com/watch?v=4r7wHMg5Yjg), Statamic don't care. You can take any of these approaches or one of many others — including several that will be invented tomorrow and forgotten by autumn. **It's up to you.** Write or generate HTML somehow and let Statamic get it to the browser. ## Path of least resistance If you don't have a hard requirement, a strong preference, or just want our advice, we recommend writing your own HTML, use [Antlers](/antlers) or [Blade](/blade) in said HTML to pull content in, use [TailwindCSS](https://tailwindcss.com) as your CSS framework, and let [Vite][vite] compile any JavaScript, SCSS/LESS, or PostCSS as necessary. You'll be able to take advantage of all of our powerful, tightly coupled [tags](/tags) that do most of the heavy lifting — like fetching and displaying content from collections and taxonomies, manipulating, assets, and rendering variables. ## Other options You don't have to go Antlers + Tailwind. At all. That's just our preference. You could do so many different things, like: - Use our [GraphQL](/graphql) integration and build your frontend with [Gatsby.js](https://www.gatsbyjs.com/) - Use our [REST API](/rest-api) and build a single page application with [Vue.js](https://vuejs.org) or [React](https://reactjs.org/) - Use [Laravel Blade](https://laravel.com/docs/13.x/blade) and some controllers and write your own routes. It's up to you. ## Request lifecycle Let's take a quick look at what happens during a typical Statamic frontend request: 1. User visits a URL. 2. Statamic checks if there's any data matching the URL (e.g. an [entry](/collections) or [route](/routing#statamic-routes)). 3. [Variables](/variables) for that item are fetched out the data store. 4. Statamic loads the appropriate [view](/views) and injects the variables into it. 5. The Contents of the rendered view is sent to the user's browser. [mix]: https://laravel.com/docs/mix [vite]: https://vitejs.dev ================================================ FILE: content/collections/pages/overview.3.md ================================================ --- id: 9a1d8b88-c600-46f2-8727-1deb56f2e87a blueprint: page title: 'Fieldtypes Overview' intro: 'Fieldtypes are customizable form [fields](/fields) used to structure your content and provide an intuitive content management experience. Each fieldtype has its own UI, data format, and configuration options.' template: page options_content: 'Each fieldtype has a common set of options in addition to any unique ones specific to that type.' options: - name: display type: text description: "The field's label shown in the Control Panel." required: false - name: handle type: text description: "The field's template variable. Avoid using [reserved words](/tips/reserved-words#as-field-names) as handles." required: true - name: instructions type: text description: 'Provide additional field instructions like this text. Markdown formatting is supported.' required: false - name: instructions_position type: text description: 'Where the instructions should be positioned relative to the field. Options: `above` or `below`.' required: false - name: variant type: text description: 'Show the field under its label or beside it. Options: `block` (Stacked), `inline` (Side by Side). Default: `block`.' required: false - name: listable type: mixed description: 'Controls whether the field should be shown in Control Panel listings. Options: `hidden`, `true`, or `false`. Default: `hidden`.' required: false - name: visibility type: mixed description: 'Controls whether the field should be shown in Control Panel publish forms. Options: `visible`, `read_only`, [`computed`](/computed-values) or `hidden`. Default: `visible`.' required: false - name: sortable type: toggle description: 'Control if the field should be sortable in listing views.' required: false - name: replicator_preview type: toggle description: 'Control preview visibility in Replicator/Bard sets.' required: false - name: duplicate type: toggle description: 'Control if the field should be included when duplicating the item.' required: false - name: actions type: toggle description: 'Show or hide field action controls, such as fullscreen mode.' required: false - name: conditions type: mixed description: 'Configure rules that control whether the field should be shown or hidden. Learn more about [conditional fields](/conditional-fields).' required: false - name: required type: boolean description: 'Control whether or not this field is required.' required: false - name: validate type: array description: 'Configure rules that validate the value of this field before allowing the user to save. Learn more about [validation](/blueprints#validation).' required: false related_entries: - 9a1d8b88-c600-46f2-8727-1deb56f2e87a - 54548616-fd6d-44a3-a379-bdf71c492c63 - 2940c834-7062-47a1-957c-88a69e790cbb - dd52c1f6-661b-4408-83c6-691fa341aaa7 - dcf80ee6-209e-45aa-af42-46bbe01996e2 updated_by: 3a60f79d-8381-4def-a970-5df62f0f5d56 updated_at: 1632748812 nav_title: Overview --- ## Overview Fieldtypes are essentially different types of form inputs you can choose from when building a [blueprint](/blueprints). They range from simple text fields and select boxes, to more complex WYSIWYG-style editors like Bard. :::watch https://www.youtube.com/embed/cs_jL6fCaA8 Watch how to add fields to a blueprint. ::: ## List of Fieldtypes Check out the full list of [all fieldtypes](/reference/fieldtypes) in our reference section. ## Data Format Fieldtypes are [augmented](/augmentation) to alter the output of your saved content according to how the field is expected to be used. For example, a [markdown field](/fieldtypes/markdown) will automatically convert your plain text input into HTML according to your markdown options of choice. Given the very same input in a [textarea field](/fieldtypes/textarea), what you enter is what you return because that fieldtype doesn't alter the content. The documentation for each fieldtype will detail if any augmentation happens. These same rules apply whether you're using [Antlers](/antlers), [Blade](/blade), [GraphQL](/graphql), or the [REST API](/rest-api). :::tip You can retrieve the original, un-augmented data by using the [raw modifier](/modifiers/raw), like so: ``` {{ markdown_field | raw }} ``` ::: ================================================ FILE: content/collections/pages/overview.4.md ================================================ --- id: bc2ad29d-50c3-47fb-9213-8553bcb5a48a blueprint: page title: 'Addons Overview' intro: 'Developers can easily build new features that are compatible with everyone’s Statamic installations. Addons can then be easily shared or sold to others to let them extend their Statamic installation.' updated_by: 3a60f79d-8381-4def-a970-5df62f0f5d56 updated_at: 1632424769 nav_title: Overview --- ## Finding addons You can browse the [Statamic Marketplace](https://statamic.com/addons) to find addons. ## Installing addons You can use Composer to install any addon: ``` shell composer require vendor/package ``` The command can be found on the addon's page in the [Statamic Marketplace](https://statamic.com/addons). :::tip Some first party addons – such as the Static Site Generator or Eloquent Driver - have their own dedicated commands which will be noted on the same pages. ```shell php please install:ssg ``` ::: ## Creating addons To learn how to create your own addon, as well as publishing it to the Statamic Marketplace, head over to the [Extending Statamic](/extending/addons) area. ## Licensing Addons may require a license, which you can purchase at the [Marketplace](https://statamic.com/marketplace). Licenses may be attached to a site in your [account area](https://statamic.com/account/sites). Make sure that you have your site key entered into your Statamic project. You can try out commercial addons locally for free. Be sure to purchase a license before deploying to production. ## Editions An addon may have multiple editions, which may cost different amounts and provide different sets of features. You can choose which edition is installed by entering it into your `config/statamic/editions.php` file: ``` php 'addons' => [ 'vendor/package' => 'pro', // e.g., 'jezzdk/statamic-google-maps' => 'pro' ] ``` ================================================ FILE: content/collections/pages/overview.5.md ================================================ --- id: 4e3ca511-fe21-497f-9166-3c7624607a91 blueprint: page title: 'Tags Overview' nav_title: Overview intro: 'Tags are Antlers expressions that give you the ability to fetch, filter, and display content, enhance and simplify your markup, build forms, and add dynamic functionality to your templates.' --- A their most basic level, Tags are PHP methods you call from your Antlers or Blade templates. They let you work with content, manipulate data, and build dynamic features without writing PHP directly in your templates. Many of them serve the same role as Controllers in a traditional MVC (Model-View-Controller) style application. ## Basic Usage Tags come in two flavors: single tags and tag pairs. Single tags are self-contained and return a value: ::tabs ::tab antlers ```antlers {{ collection:blog }}

    {{ title }}

    {{ /collection:blog }} ``` ::tab blade ```blade

    {{ $title }}

    ``` :: Tag pairs wrap content and can structure and manipulate what's inside: ::tabs ::tab antlers ```antlers {{ entries:listing folder="blog" }}

    {{ title }}

    {{ excerpt }}

    {{ /entries:listing }} ``` ::tab blade ```blade

    {{ $title }}

    {{ $excerpt }}

    ``` :: ## What Tags Can Do Tags handle the heavy lifting in your templates: - **Fetch content** — Get entries from collections, taxonomies, globals, and more - **Filter and sort** — Narrow down results with powerful query parameters - **Manipulate data** — Transform strings, arrays, dates, and other values - **Build forms** — Create and handle form submissions - **Control logic and flow** — Conditionally show content, loop through data, and more - **Work with assets** — Resize images, generate responsive srcsets, and manage files ## Common Tags Some tags you'll use frequently: - `{{ collection:* }}` — Fetch entries from collections - `{{ entries:listing }}` — List and filter entries - `{{ taxonomy:* }}` — Work with taxonomy terms - `{{ assets:* }}` — Handle images and files - `{{ form:* }}` — Build and process forms - `{{ if }}` / `{{ unless }}` — Conditional logic - `{{ partial }}` — Include reusable template snippets Browse the [full tag reference](/tags/all-tags) to see everything available, or [build your own custom tags](/tags/building-a-tag) when you need something specific. ================================================ FILE: content/collections/pages/overview.6.md ================================================ --- id: 9c1efbc5-c6a4-46f1-acce-d38b20122bd6 blueprint: page title: 'Modifiers Overview' intro: 'Modifiers manipulate the data of your variables on the fly in Antlers templates. They can modify strings, filter arrays and lists, perform comparisons, handle basic math, simplify your markup, and even help you debug.' nav_title: Overview --- ## Overview Modifiers are available exclusively in [Antlers][antlers] templates. Each modifier is a function that accepts the value of the variable it's attached to, can do just about anything with that data, and then returns it. Multiple modifiers chained onto a variable will be executed in sequence, each passing its modified value onto the next. ## Example You could take some text, render it as markdown, uppercase it, and ensure there are no widows (lines with only one word on them) like this: ::tabs ::tab antlers ```antlers // This... {{ "Ruth, Ruth, Ruth! Baby Ruth!" | markdown | upper | widont }} // Becomes this!

    RUTH, RUTH, RUTH! BABY RUTH!

    ``` ::tab blade ```blade {!! Statamic::modify("Ruth, Ruth, Ruth! Baby Ruth!")->markdown()->upper()->widont() !!} // Becomes this!

    RUTH, RUTH, RUTH! BABY RUTH!

    ``` :: ## Related reading Eager for more knowledge? Check out [Antler's modifier syntax](/antlers#modifying-data) and discover how to [build your own](/extending/modifiers#creating-a-modifier). ## Core modifiers You can find a [full list of modifiers](/reference/modifiers) included with Statamic in the reference section. [antlers]: /antlers ================================================ FILE: content/collections/pages/overview.7.md ================================================ --- id: 4b77c19b-129c-4271-a724-eea884eb3e2e blueprint: page title: 'Widgets Overview' nav_title: Overview intro: > The Control Panel's dashboard may contain any number of widgets. A widget is simply a box that shows something. That something might be anything from a list of recently updated entries, to a randomized inspiration quote, and anything in between. ---
    Widgets Overview Widgets Overview
    Look at me. I'm the Captain now.
    Statamic comes bundled with a handful of widgets, however you may also [create your own](/extending/widgets) or use ones [created by others](https://statamic.com/addons/tags/widget). ## Configuration Widgets can be added to the dashboard by modifying the widgets array in the `config/statamic/cp.php` file. Each item in the array should specify the widget as the type, plus any widget-specific configuration values. You may use the same widget multiple times, configured in different ways. ## Available widgets Check out the [list of available widgets](/reference/widgets) included in Statamic Core. ================================================ FILE: content/collections/pages/overview.8.md ================================================ --- id: 0b4733ea-4ca9-4bb9-9df1-f1ab95553dfe blueprint: page title: Variables Overview nav_title: Overview intro: Context-aware variables are always available in your [views](/views), giving you access to dynamic information about the current URL, user, loaded entry, site settings, and more. template: variables.index --- ## Overview Where appropriate, Statamic will inject data automatically for you to use in your views. For example, when a view is loaded, you get automatic access to the variables applicable to it. You don't need to use a tag to "get" the data. If you're viewing an entry's URL, all of the [entry variables](#entry-variables) will just be there. If you use a tag that does supply some data, it will typically make those variables available. The [collection tag](/tags/collection) will loop over entries, again giving you access to entry variables within the loop. The [assets tag](/tags/assets) gives you [asset variables](#asset-variables), the [taxonomy tag](/tags/taxonomy) gives you [term variables](#term-variables), and so on. The same is true for within tag pairs of [augmented values](/augmentation). Looping through a field configured to use an assets fieldtype? You'll be getting asset variables. ## Reaching into the cascade Let's say you're on an entry's URL and you're looping through related entries. Within the loop, you'd have a `{{ title }}` which would be for the entry in that loop. But what if you want to get the `{{ title }}` from further up your view? ### The current page scope Variables for the current page will be aliased into a `page` array. You can access this any time by prefixing a variable with `page:`. ``` {{ related_posts }} {{ title }} // The title of the entry in the loop. {{ page:title }} // The title of the entry used when loading the URL. {{ /related_posts }} ``` ### Explicitly defined scopes You aren't limited to the `page` scope. You can use the `{{ scope }}` tag to take a "snapshot" of the variable context at any point of the template and use it for reference elsewhere. For example, we can create a scope named `stuff`. ``` {{ scope:stuff }} {{ title }} {{ collection:blog }} {{ title }} // The title of the entry in the loop. {{ stuff:title }} // The title variable at the time the scope tag was used. {{ /collection:blog }} {{ /scope:stuff }} ``` ## Globals You can create your own [Global variables](/globals), which all get injected into the variable cascade, ready to be used in your views. ## View front-matter Inside Antlers views, you may define YAML front-matter. This may be a handy way to define variables without needing to add anything to content or blueprints. To access this data, prefix the variables with `view:`. ``` --- foo: bar --- {{ view:foo }} ``` ```html bar ``` :::tip You **must** define any front-matter variables at the top of the view file, even before things like Antlers comments. ::: ## Available variables The following groups of variables are available in your views, depending on their context. ================================================ FILE: content/collections/pages/overview.md ================================================ --- id: 20f0707b-619d-4a7a-b7dd-aea4122fa1db blueprint: page title: 'Content Modeling' intro: 'Before you build pages, templates, or implement features, there’s one foundational question to answer: **what shape should your content take?**' related_entries: - 54548616-fd6d-44a3-a379-bdf71c492c63 - 2940c834-7062-47a1-957c-88a69e790cbb - 1e91dd54-c452-4e3b-8972-dba83c048d3d - 7202c698-942a-4dc0-b006-b982784efb03 nav_title: Overview --- Content modeling is just the process of deciding how your content is structured — what fields it has, how different pieces relate to each other, and where flexibility actually matters. In Statamic, that plays out with blueprints, fields, globals, collections, and relationships. A good content model makes everything easier: - Editors understand what goes where - Developers have more flexibility to pull the content you want into all the right places - Designers don't have to be told "we can't do that" - And future you doesn’t regret past you’s shortcuts This guide walks through how to think about content first — separating structure from presentation, optimizing for changeability and flexibility, and building a site that doesn't revolve around _pages_, but well-structured content. ## The Separation of Content and Presentation Content professionals have become accustomed to thinking about content and presentation together. They expect to see what the content will look like and often expect to change that appearance as well. Traditional WYSIWYG tools and visual builders reinforce this mindset. They blur the line between content and layout, and in doing so, let design decisions leak into the content itself. But these are bandaids. An author shouldn't be making decisions about layout — that's the job of the designer and/or UX professional. An author should focused on the **clarity of content**, not enforcing (or deciding) style. The job of the CMS (Statamic in this case) is to take that content, manage it well, and give designers and developers the freedom to present it however they need — today or years from now. When you separate content from presentation, you stop tying your data to a specific layout. You don’t have to rewrite everything when a design changes. You don’t have to migrate content just because a homepage gets redesigned. Your content is adaptable. You did the hard work once and now the rest of the work becomes easy. This is where Statamic really shines, and it’s also why you won’t find an Elementor-style visual builder anywhere around here. Those tools are crutches — shortcuts at best, and long-term liabilities at worst. They tend to lock content into a moment in time and replace thoughtful design with convenience. Alright. Soap box over. Let’s get practical. ## Start with Collections First start by determining all of your different **"content types"**. These usually map directly to [collections](/collections). For example, if your site is going to have articles, news, case studies, and a handfull of on one-off pages, you'll probably end up with collections like: - Articles - News - Case Studies - Pages. Each collection represents a distinct type of content with its own purpose, structure, and lifecycle. As you do this, watch for content that feels reusable. If something shows up in multiple places, it may deserve its own collection. For example, on a marketing site for a software product, you might reference specific “features” across articles, case studies, and landing pages. Instead of rewriting that content everywhere, you can model features as their own collection and pull them into other entries using [relationship fields](/relationships). ## Then Define Your Fields Every collection uses one or more [blueprints](/blueprints). Blueprints define the fields that make up that content. If multiple blueprints need the same fields, group them into a [fieldset](/fieldset) and import it where needed. When defining fields, think beyond “what do we need right now?” and more in terms of “how might this content be reused or rearranged later?” If a piece of content could reasonably stand on its own — be styled differently, moved elsewhere, or displayed independently — it probably deserves its own field. Consistency matters. Use clear, predictable naming conventions across your blueprints and fieldsets. Things like `body_content`, `hero_image`, `sidebar_callout`. Future you says thank you. Small decisions like this add up. A clean, consistent content model is easier to understand, easier to extend, and far less likely to be “creatively worked around” by frustrated editors. ## Global Variables for everything else Finally, there are [globals](/globals). If something lives in one place but is used across the site — like header content, footer links, social profiles, or site-wide calls to action — globals are the right tool for the job. They keep shared content centralized, editable, and out of places where it doesn’t belong. ## What to Avoid Most content modeling problems don’t show up on day one. They show up months later, when the site grows, the design changes, or someone asks, “Can we reuse this somewhere else?” Here are some common traps to avoid. ### Modeling Pages Instead of Content If your content model mirrors your page layouts, you’re probably heading for trouble. Fields like `left_column_text`, `homepage_feature_1`, or `about_page_body` are a code smell. They bake presentation decisions directly into the content and make reuse painful or impossible. As soon as `left_column_text` ends up needing to be used on the right side somewhere, a fairy dies — and no amount of clapping can revive her. Model what the content **is**, not where it happens to live today. ### Over-Modeling Everything Not everything needs to be perfectly modeled. If a piece of content is truly one-off, short-lived, or unlikely to be reused, don’t turn it into a dozen fields just because you can. Over-modeling can create friction and slow people down. Model for clarity and flexibility — not theoretical perfection. ### Ignoring Future Change The biggest mistake is assuming today’s structure is permanent. Sites evolve. Designs change. Content gets reused in ways you didn’t anticipate. It gets pulled into mobile app or powers a customer-support ticketing app. A good content model leaves room for that without forcing a rewrite or migration. If your model _only_ works for the site you’re building right now, it’s probably too brittle. ================================================ FILE: content/collections/pages/permissions.md ================================================ --- title: Permissions template: page updated_by: 42bb2659-2277-44da-a5ea-2f1eed146402 updated_at: 1569347255 id: ff397ebf-4b53-4dbd-b81b-0dec839e0e5f --- Permissions are the abilities that can be assigned to [Roles](/users#permissions). Out of the box, Statamic has its own set of permissions that you can choose from to configure your roles. However, you are free to add your own that can be used throughout your project, or included with addons. ## Basic permissions You can register a basic permission in a service provider by specifying the string. Make sure to surround any permission registrations in a `Permission::extend` closure. ``` php use Statamic\Facades\Permission; public function boot() { Permission::extend(function () { Permission::register('manage stuff') ->label('Manage Custom Stuff'); }); } ``` This will add an option to the permissions list when editing a role in the Control Panel. If selected, this will add the permission string to the role: ``` yaml permissions: - manage stuff ``` ## Nested permissions It could be useful to only allow some permissions if others have already been granted. For example, you want a tree like this: ``` files theme:serendipity-light view blog entries edit blog entries create blog entries delete blog entries ``` Initially, only the `view` option will be selectable. When you check it, then the `edit` option becomes selectable. Check that, and the `create` and `delete` options become selectable. This can be achieved by passing an array of permissions to the `children` method on the parent permission: ``` php Permission::register('view blog entries', function ($permission) { $permission->children([ Permission::make('edit blog entries')->children([ Permission::make('create blog entries'), Permission::make('delete blog entries') ]) ]); }); ``` The second argument of the `register` method accepts a closure that allows you to modify the permission. ## Policy based permissions When dealing with a permission that could apply to a variable number of items, it makes more sense to use a [Policy](https://laravel.com/docs/authorization#creating-policies). You may combine your policy with a wildcard permission. A new permission will be created for each item you require. For example, Statamic creates a `view {collection} entries` permission for each collection that exists. It does this by using a `replacements` method to return a list of items to determines the replacements. It should return an array of arrays where `value` is the string to be inserted into the permission, and a `label` to be inserted into the label. ``` php Permission::register('view {collection} entries', function ($permission) { $permission ->label('View :collection entries') ->replacements('collection', function () { return Collection::all()->map(function ($collection) { return [ 'value' => $collection->handle(), 'label' => $collection->title() ]; }); }); }); ``` To use your policy permissions, you should write the authorization checks from within a Policy class. For example: ``` php class EntryPolicy { public function edit($user, $entry) { return $user->hasPermission("view {$entry->collectionName()} entries"); } } ``` Finally, you may combine policy wildcard permissions with nested permissions. ``` php Permission::register('view {collection} entries', function ($permission) { $permission ->label('View :collection entries') ->replacements('collection', function () { /* ... */ }); ->children([ Permission::make('edit {collection} entries')->children([ Permission::make('create {collection} entries'), Permission::make('delete {collection} entries') ]) ]) }); ``` :::tip When using replacements, ensure your `label` string contains a placeholder prefixed with a colon. ::: ## Groups You can put your permissions in your own group. Give it a name, a label, and then any permissions created inside the callback will be added to that group. ``` php Permission::group('myaddon', 'My Addon', function () { Permission::make(...); }); ``` If you want to add permissions to an existing group (eg. the core ones like collections, taxonomies, etc.) you can just leave out the label argument: ``` php Permission::group('collections', function () { Permission::make(...); }); ``` ## Adding to the core permissions It's possible to add to the built-in permission tree if you need to. For example, maybe you want to add a permission to send tweets once an entry is published. You might want to jam that in every collection's permission tree under its 'edit' permission. You can use the `addChild` method on an existing permission to inject it at that position. ``` php Permission::extend(function () { Permission::get('edit {collection} entries')->addChild( Permission::make('tweet {collection} entries') ); }); ``` ## Overriding Policies You may override policies by registering a binding in your AppServiceProvider. ```php public function register() { $this->app->bind( \Statamic\Policies\EntryPolicy::class, \App\Policies\CustomEntryPolicy::class ); } ``` ```php class CustomEntryPolicy extends \Statamic\Policies\EntryPolicy { public function edit($user, $entry) { // ... } } ``` Keep in mind that most of Statamic policies will grant access earlier if the user is a super user. If you need to disable or override the super user logic, you will need to also adjust the `before` method. For example: ```php class CustomEntryPolicy extends \Statamic\Policies\EntryPolicy { public function before($user, $ability) // [tl! **:start] { if ($ability === 'edit') { // Returning null here will allow the method to be called. return null; } return parent::before($user, $ability); } // [tl! **:end] public function edit($user, $entry) { // ... } } ``` ================================================ FILE: content/collections/pages/ploi.md ================================================ --- id: cf38dba4-5cce-4b81-a2f5-e82665e4e11f blueprint: page title: 'Deploying Statamic with Ploi' intro: |- Ploi provisions and deploys PHP applications on DigitalOcean, Linode, Vultr, Amazon, Hetzner and other hosting platforms. It's a piece of 🍰 to deploy a Statamic site with it. parent: c4f17d05-78bd-41bf-8e06-8dd52f6ec154 --- :::tip Use the coupon `RAD-DEPLOIMENT` to get 25% off your Ploi subscription. Can be used once per account and only works with the manual renewal and a duration of up to five months. ::: Assuming you have a [Ploi](https://ploi.io) account, the first thing to do is authorize your hosting provider of choice. In this walk-through we'll use [Hetzner](https://www.hetzner.com) as the example. This is a one-time step and will allow you to easily spin up and provision new server stacks anytime. Go to your [Hetzner Cloud Console](https://console.hetzner.cloud) (or other cloud provider of choice) and create an API token. Check out the [Hetzner docs on generating API tokens](https://docs.hetzner.com/cloud/api/getting-started/generating-api-token/) if you need it. Make sure the token has **read and write** access.
    Deployment hosting setup example
    ## Spinning Up a New Server Once you have connected to your hosting provider, the next step is to spin up a new server. Ploi automatically tailors the server stack for Statamic and Laravel, so you only need to choose the server size most suitable for your project and you'll be billed accordingly by Hetzner.
    Create server example
    ## Creating a New Site The next step is to create a new site. This will scaffold out the directory structure and nginx config on the server, and further allow you to configure your site's environment variables, deployments, and so on.
    Create site example
    ## Configuring Deployment
    Install repo example
    Finally, setup your deployment by pointing your site to your source control repository. Ploi will create a sensible deployment script for you for one-click deployments. Alternatively, you can use Ploi's [1-click Statamic install](https://ploi.io/statamic) feature to quickly create a new site and an optional repository. You can even use this feature with one of [Statamic's starter kits](https://statamic.com/starter-kits).
    Install Statamic with Ploi's 1-click feature example
    Whip up a fresh install right from the Ploi dashboard 🚀
    After doing this, you'll be able to customize the deployment script if needed. You can also enable "**quick deploy**", which will automatically trigger deployments when you push changes to your chosen branch. You can also [use GitHub actions to trigger a deployment](https://ploi.io/documentation/deployment/how-to-trigger-deployments-via-github-actions) with Ploi.
    Deployment script example
    The "Deploy script" area is where you'd add commands to install Composer and NPM dependencies, compile CSS and JavaScript if you need to, and clear Statamic's cache. Most deploy scripts look something like this: ``` shell cd /home/ploi/{example}.{tld} git pull origin main composer install --no-interaction --prefer-dist --optimize-autoloader echo ... sudo-S service php8.1-fpm reload php please cache:clear npm ci && npm run production ``` If you're planning on using the Git integration, you may want to prevent content changes from the Control Panel from triggering "full" deployments in Ploi. Learn more about this on the [Git Automation](/git-automation#customizing-commits) page. ## Statamic specific features Ploi lets you interact with your Statamic installation without you having to connect to your server via SSH. This includes clearing the (static) cache, warming the stache, generating meta data for assets etc.
    Ploi's Statamic specific features
    Access the Statamic and Laravel CLI right from the web UI.
    ## Advanced Control Ploi is [optimized for Laravel](https://ploi.io/laravel-optimized) and offers advanced control of queue workers, cron jobs, SSL certificates, database access, and more.
    Advanced Ploi features
    Ploi has a lot of handy features.
    ================================================ FILE: content/collections/pages/preferences.md ================================================ --- id: 452c268b-b885-4deb-8e46-1cc3ebc66e4f blueprint: page title: Preferences intro: Preferences are easy to manage settings available from and generally affecting only the inside of the control panel. They can be set differently per-user, role, and globally. --- Where application configuration lives in PHP config files, preferences can be accessed from the control panel where they can be edited by clients or users. The actual preferences themselves are stored in YAML files, whether on the user, role, or [default preferences file](#storage). ## Accessing preferences Users can access preferences through the cog icon in the upper right hand corner of the CP.
    CP Preferences CP Preferences
    Manage your own preferences!
    ## Customizing preferences for other users In order to customize preferences for other users, you must first enable [Statamic Pro](/tips/how-to-enable-statamic-pro), and you must either be a super user or have permissions to manage preferences.
    Manage Preferences Permission Manage Preferences Permission
    Are you rad enough to manage global preferences?
    This will allow you to customize the default preferences for all users, or on a role-by-role basis, though end-users will still have the ability to further customize their own CP nav as they see fit.
    Preferences for Other Users Preferences for Other Users
    Manage the preferences for other users!
    ## Precedence You may specify different sets of preferences, which will override each other appropriately. - Default preferences, which apply to everyone. - Role preferences, which apply to users assigned to that role. - User preferences, which only apply to that user. :::tip Heads up Since a user may have multiple roles, they will inherit the preferences of their primary (or first) role. ::: ## Storage Default preferences are stored in `resources/preferences.yaml` as a simple array. ```yaml locale: en start_page: collections/articles ``` Role and user preferences are stored in their existing respective locations as the same array in a `preferences` key. ## Themes Themes let you control the look and feel of the Control Panel. Choose from community-made themes, or create your own by assigning colours from the Tailwind palette to each UI role. Selecting a theme previews it instantly, updating the Control Panel in real time. Custom themes can be shared and published for others to install.
    Theme Preferences Theme Preferences
    ## Adding fields You may add additional preference fields from within a service provider. The closure should return an array that has sections (tabs) at the top level, and each section should have a `fields` array that contains all the field definitions. ```php public function boot() { Preference::extend(fn ($preference) => [ 'extras' => [ 'display' => __('Extras'), 'fields' => [ 'color' => [ 'type' => 'text', 'display' => __('Color'), ], 'size' => [ 'type' => 'select', 'display' => __('Size'), 'options' => [ 's' => __('Small'), 'm' => __('Medium'), 'l' => __('Large') ], ], ] ] ]); } ``` :::tip If you don't want to put a field in an additional section, you can place it in the `general` section. The tab label will only be visible when there are more than one. ::: ## Getting and setting values ### Using PHP You can get a preference, with an optional fallback value to be returned if the preference isn't set. This will respect the default/role/user cascade. ```php Preference::get($key, $fallback); ``` To set a value, you should set it in the respective area, then save it. ```php Preference::default()->set($key, $value)->save(); $role->setPreference($key, $value)->save(); $user->setPreference($key, $value)->save(); ``` ### Using JavaScript You can get and set values using JavaScript too. ```js this.$preferences.get(key, fallback); ``` Setting values will perform an AJAX request, so you will need to wait until it's completed. ```js this.$preferences.set(key, value).then(response => { // do something once the ajax request completes }); ``` :::tip Setting values from JS can only apply it to the user's preferences. ::: ================================================ FILE: content/collections/pages/progress.md ================================================ --- title: Progress intro: | Control the magic progress bar at the top of the page. id: 28068f9a-f269-4646-87e4-881e5477558d --- You can control the progress bar at the top of the page through the `$progress` instance method. This progress bar will get a little further in small intervals automatically but will never reach 100% until it's told to. The component can track the progress from multiple places, and will only be considered complete once all of them are complete. ``` js import { progress } from '@statamic/cms/api'; progress.start($name); // Starts the progress bar progress.complete($name); // Instantly progress to 100% and disappear progress.loading($name, true); // Alias of .start() - Useful for passing a boolean progress.loading($name, false); // Alias of complete() progress.names(); // The names of the items that are being tracked. progress.count(); // How many are being tracked. progress.isComplete(); // Whether all the items that were being tracked have completed. ``` :::tip If you have a component that may appear multiple times on one page (like a Fieldtype), make sure the name is unique. You could use the browser's crypto API for this: ``` js const uniqueId = crypto.randomUUID(); progress.start(`things-${uniqueId}`); ``` ::: ================================================ FILE: content/collections/pages/protecting-content.md ================================================ --- title: 'Protecting Content' intro: It's common to want to put a site online before it's ready to be viewed by the public. Statamic has built-in ways of making this very easy for you. template: page blueprint: page id: 75be125b-7d92-496c-ac5d-7098560d3d44 --- ## Overview You may deny front-end access to your content on a **per-page**, **per-collection**, or **site-wide** basis. There are a number of drivers for protecting content available out of the box: - [auth](#authentication) for only allowing authenticated users. - [ip_address](#ip-address) for allowing specific IP addresses. - [password](#password) will force users to enter a specified password. You can also [create your own drivers](#custom-drivers). Whichever approach you choose, know that it's designed to help you out. We’ve tried to keep the syntax as simple as possible while allowing for flexibility. Because of this, if Statamic sees you’ve set `protect`, but the scheme has been configured incorrectly, _all users will be denied_. ## Caveats * Protection only applies to the frontend of your site routed through Statamic (like entry URLs). Custom routes defined in your `routes/web.php` file and the Control Panel will be unaffected. * Protected pages are automatically excluded from the [static cache](/static-caching#important-preface), unless the driver explicitly opts in by [marking itself cacheable](#cacheable-drivers). ## Protecting an entry To protect an entry, add a `protect` variable with a corresponding scheme name. For example, Statamic comes pre-configured with a `logged_in` protection scheme that only shows the content to authenticated users. You might have an entry like this: ``` yaml --- title: Members Only protect: logged_in --- When visiting this entry's URL, logged in users will see it, but logged out users will be redirected to a login page. ``` ## Protecting a collection To protect an entire collection, inject a `protect` variable into your collection. To do this, add the following to your collection's YAML config file. This cannot be done in the control panel. ``` yaml --- inject: protect: logged_in ``` ## Configuring schemes The configuration file is located at `config/statamic/protect.php`. In this file you may specify a number of different schemes which you can reference throughout your content files. You are free to use the same driver in multiple schemes, configured in different ways. Check below for details on how to configure each driver. ## IP address Add the IP address(es) you wish to allow to the aptly named `allowed` array. ``` php 'ip_address' => [ 'driver' => 'ip_address', 'allowed' => ['127.0.0.1', '192.168.0.10'], ] ``` ## Authentication Adding this scheme to a page will redirect to a login page unless the user is already logged in as a Statamic user. ``` php 'logged_in' => [ 'driver' => 'auth', 'login_url' => '/login', 'append_redirect' => true, ] ``` If the `login_url` has not been defined the user will see an “Access Denied” page instead of a login screen. In this case, the user could log in through the Control Panel and then come back. The `append_redirect` setting will add `?redirect=/the-protected-url` to your login_url. This pairs with the [user:login_form tag’s allow_request_redirect parameter](https://docs.statamic.com/tags/user-login_form#parameters) which will redirect the user to the intended page once successfully logged in. This protection method _does not_ take into account any user roles. They are simply either logged in or they're not. ## Password This is perfect for times when you want to password-protect one or more files but don’t want to set up user accounts for this one purpose. This scheme does not relate to member accounts in any way. ``` php 'password' => [ 'driver' => 'password', 'allowed' => ['secret', 'confidential'], 'form_url' => null, ] ``` You can also define the password for a protected entry on the entries themselves. This might be helpful if each has a different password. ```yaml # content/collections/pages/secret-page.md protect: password password: local-password ``` ``` php // config/statamic/protect.php 'password' => [ 'driver' => 'password', 'allowed' => ['secret', 'confidential'], 'form_url' => null, 'field' => 'password', // [tl! add] ] ``` ### Password form
    A Statamic password protected page A Statamic password protected page
    The default password protected login screen.
    You’ll need to provide a way for people to enter passwords for URLs. Statamic has a built-in generic password entry form. If you want to customize it, you have two options: #### Override the view ::tabs ::tab antlers Override the view by creating `vendor/statamic/auth/protect/password.antlers.html` in your `views` directory, and use the [protect:password_form](/tags/protect-password_form) tag to build a form. No config change required. For example: ```antlers {{ protect:password_form }} {{ if no_token }} No token has been provided. {{ else }} {{ if error }}
    {{ error }}
    {{ /if }} {{ errors:password }}
    {{ value }}
    {{ /errors:password }} {{ /if }} {{ /protect:password_form }} ``` ::tab blade Override the view by creating `vendor/statamic/auth/protect/password.blade.php` in your `views` directory, and use the [protect:password_form](/tags/protect-password_form) tag to build a form. No config change required. For example: ```blade @if ($no_token) No token has been provided. @else @if ($error)
    {{ $error }}
    @endif @if (isset($errors['password'])) @foreach ($errors['password'] as $error)
    {{ $error }}
    @endforeach @endif @endif
    ``` :: The `protect:password_form` tag is going to wrap everything between the tags in an HTML form tag pointing to the appropriate place. The HTML of the form itself is up to you. The only requirement is to name the password input `password`. You can do anything else you want. #### Custom form URL If you would like more control over the location of the password form, you may change `form_url` in the scheme's config: ``` php 'form_url' => '/password-entry' ``` You can create a page or a route for this. In the corresponding view, you can build a form as described above. ### Validation errors When a validation error is encountered, `error` and `errors` variables will be available to you. The `error` variable will be a string with the first error, useful if you want to display an error at the top of your form. The `errors` variable will be an array keyed by field names, each containing an array of messages. This is useful for _inline_ errors. ### Token When visiting a password protected page, Statamic will generate a token and append it to the form’s URL. Without a token, the form cannot function correctly. In the example above, you can see the `no_token` boolean will be populated for you. This may happen if you visit the form URL directly. ### Invalid passwords If someone submits a password and it isn’t valid, Statamic will redirect back with the appropriate validation error. Valid passwords can vary from piece of content to piece of content. This one form is smart enough to handle all password management between password-protected URLs. ### Valid passwords A valid password is anything matching one of the passwords in the allowed list as configured on the scheme. This means that you can send three people three different passwords to access the same URL, each having their own way in. Additionally, you could also set just one password and send that to 100 people and they can all use the same password. As always with online security, be careful with who you share passwords with or you'll find yourself changing them often. :::warning This protection method is meant for short-term access control. For example, showing a client your progress without the public or to prevent Google from indexing a staging site. It's about as secure as curtain over an open window: just good enough for passer-bys. ::: ### Password expiration Each user’s passwords will expire along with their session. To manually invalidate a password, remove it from the list of allowed passwords on the page. The next time a user with that password visits this page they’ll be redirected to the password form just like everyone else. ## Endgame protection If you want to protect a page from anyone - regardless of authentication status, IP address, time of day, weather, or beverage preference - you can simply add `protect: true` to the entry's front-matter. One may find this useful to quickly disable something. ## Site-wide protection To protect your whole site at once, add a scheme name to `default` in your `protect.php` configuration file. For example, to make sure your whole site is only accessible to a single IP address, you could add: ``` php 'default' => 'test', 'schemes' => [ 'test' => [ 'driver' => 'ip_address', 'allowed' => ['127.0.0.1'] ] ] ``` ## Custom drivers ### Writing the driver To create your custom protection driver, you should extend the `Statamic\Auth\Protect\Protector` class and add a `protect` method. The protect method should typically: - Call `abort(403)` to deny access. - Call `abort(redirect($url))` to redirect somewhere (eg. how the auth driver redirects to a login page) - Do nothing, which would allow access. Here's a silly example that will randomly allow or deny access: ``` php scheme; // The name of the scheme. $this->config; // The configuration array of the scheme. $this->url; // The URL the protection was triggered on. $this->data; // The data object (eg. the Entry) being protected. ``` ### Cacheable drivers By default, any page using a protection scheme is excluded from the [static cache](/static-caching) — Statamic adds an `X-Statamic-Protected` header to the response which prevents caching. This is a safe default because the first visitor's view of a protected page would otherwise get served to everyone. If your custom driver's protection logic doesn't depend on the visitor (for example: a scheme that only allows access during a specific date range, or only on certain environments), you can opt it into static caching by overriding the `cacheable` method and returning `true`. ``` php lt('2026-01-01'), 404); } public function cacheable() { return true; } } ``` When `cacheable()` returns `true`, the `X-Statamic-Protected` header will not be added and the page is eligible for the static cache. :::warning Only mark a driver as cacheable when its `protect()` logic produces the same outcome for every visitor. User-specific, IP-specific, or password-based protection must not be cached. ::: ### Registering the driver Inside a service provider's `boot` method, you can use the `extend` method on the protector manager class. ``` php use Statamic\Auth\Protect\ProtectorManager; app(ProtectorManager::class)->extend('coin_flip', function ($app) { return new CoinFlip; }); ``` The first argument passed to the `extend` method is the name of the driver. This will correspond to your `driver` option in the `protect.php` configuration file. The second argument is a Closure that should return an `Protector` instance. The Closure will be passed an $app instance, which is an instance of the service container. Once your extension is registered, update your `protect.php` configuration file's `driver` option to the name of your extension. ================================================ FILE: content/collections/pages/publish-forms.md ================================================ --- title: 'Publish Forms' intro: | Build custom forms by harnessing the power of Blueprints and Fieldtypes. id: b4b46ceb-9feb-4587-8f0d-2080511bf9e3 --- ## Overview When creating or editing content (entries, pages, etc), you are presented with a form view. This is what we call the "Publish" form. You're free to use these in your own addons or custom features. The publish form flow looks like this: - Get a blueprint - Get some data - Blueprint performs some pre-processing on the data - Pass them both along to a Vue component - User hits save - Blueprint does some validation - Blueprint does some post-processing on the data - Do something with the data The required components depends on the complexity of what you're building. - Very simple forms may not need any Vue or JavaScript at all, and could simply use the `PublishForm` class directly from your controller. - If you need JavaScript or Vue, the `PublishContainer` component can be paired with blueprint data to render an entire form. - The `PublishContainer` component can have its contents overridden if you need more control over the layout or behavior of the form. ## Simple Forms You can create a basic Publish Form without having to think about Vue or Blade. You'll need a route and a controller. The controller needs to get the blueprint and its values, as well as store the updated values. For example, if you wanted to create a Publish Form for an Eloquent model, the code might look like this: ```php use Statamic\Facades\Blueprint; class Product extends Model { public function values(): array { return [ 'name' => $this->name, 'description' => $this->description, ]; } public function blueprint() { return Blueprint::make(...); } } ``` ```php Route::get('products/{product}', [ProductController::class, 'edit'])->name('product.edit'); Route::patch('products/{product}', [ProductController::class, 'update'])->name('product.update'); ``` ```php use App\Models\Product; use Illuminate\Support\Request; use Statamic\CP\PublishForm; class ProductController { public function edit(Product $product) { return PublishForm::make($product->blueprint()) ->values($product->values()) ->submittingTo(cp_route('product.update', $product)); } public function update(Request $request, Product $product) { $values = PublishForm::make($product->blueprint())->submit($request->all()); $product->update($values); } } ``` The `PublishForm` class accepts various other methods: | Method | Description | |-------------------------------|-------------------------------------------------------------------| | `title($title)` | Title of the publish form page. | | `icon($icon)` | Icon to be shown in the header, next to the page title. | | `values($values)` | The publish form values. | | `parent($parent)` | Provides a "parent" object to the fieldtypes | | `readOnly()` | Marks the publish form as read-only. | | `asConfig()` | Marks it as a "config" form, which renders slightly differently. | | `submittingTo($url, $method)` | Specify the submission URL and HTTP method (defaults to `PATCH`). | ## Complex Forms For more complex forms, you can use the underlying components to build out the functionality you need. You'll need a route and controller on the backend, and a Vue component on the frontend responsible for holding the form's values and submitting them somewhere. ### Preparing for the front-end For example's sake, we'll be using the publish form to update Eloquent models (a `Product` model), much like a typical Laravel application. ```php Route::get('products/{product}', [ProductController::class, 'edit'])->name('product.edit'); Route::patch('products/{product}', [ProductController::class, 'update'])->name('product.update'); ``` ``` php use App\Models\Product; use Inertia\Inertia; public function edit(Product $product) { // Get an array of values from the item that you want to be populated // in the form. eg. ['title' => 'My Product', 'slug' => 'my-product'] $values = $product->toArray(); // Get a blueprint. This might come from an actual blueprint yaml file // or even defined in this class. Read more about blueprints below. $blueprint = $this->getBlueprint(); // Get a Fields object, a representation of the fields in a blueprint // that factors in imported fieldsets, config overrides, etc. $fields = $blueprint->fields(); // Add the values to the object. This will let you do things like // validation, and processing, which is about to happen. $fields = $fields->addValues($values); // Pre-process the values. This will convert the raw values into values // that the corresponding fieldtype vue components will be expecting. $fields = $fields->preProcess(); // You'll probably prefer chaining all of that. // $fields = $blueprint->fields()->addValues($values)->preProcess(); // We're returning a Vue component here with Inertia. We're passing // the blueprint, the values and the meta. return Inertia::render('app::Products/Edit', [ 'blueprint' => $blueprint->toPublishArray(), 'initialValues' => $fields->values(), 'initialMeta' => $fields->meta(), ]); } ``` :::tip If you haven't already, now is a good time to [set up JavaScript & Vite](https://v6.statamic.dev/control-panel/css-javascript) for the Control Panel. ::: ### The front-end Statamic provides a `PublishContainer` component, which is the workhorse of any publish form. Most of the time, you can use it self-closed with some props, and it will render exactly what you need. ```vue ``` The Publish Container will render any tabs, sections and fields appropriately based on the provided `blueprint`. You may customize the layout of the form by providing slot content. ```html ``` Please see our [UI Component docs](https://statamic.dev/?path=/docs/components-publishcontainer--docs&args=icon:hr) for full information on the available props and events. ### Handling the form submission The `SavePipeline` pairs with a `PublishContainer` to save your data, render any validation errors, fire hooks, etc. The data from your Publish Container will be sent `through` the steps. The only required step is the `Request`. You provide the pipeline class with a reference to the Publish Container, the saving state, and errors, and it will update them for you appropriately. You may provide additional steps, such as the `AfterSaveHooks` here. Once everything is done, the `then` callback will be run, like a promise. Any errors can be caught in the `catch` callback. If the pipeline is intentionally stopped, `e` will be an instance of `PipelineStopped`. ```vue ``` In your controller, you'll need to get the blueprint, validate the values and process them before updating your model. ```php use App\Models\Product; use Illuminate\Http\Request; public function update(Request $request, Product $product) { $blueprint = $this->getBlueprint(); // Get a Fields object, and populate it with the submitted values. $fields = $blueprint->fields()->addValues($request->all()); // Perform validation. Like Laravel's standard validation, if it fails, // a 422 response will be sent back with all the validation errors. $fields->validate(); // Perform post-processing. This will convert values the Vue components // were using into values suitable for putting into storage. $values = $fields->process()->values(); // Do something with the values. Here we'll update the product model. $product->update($values); // Return something if you want. But it's not even necessary. } ``` You've just rendered an item in a Publish Form and handled updating it! Give yourself a pat on the back. 👏 :::tip Since the values are being processed through the blueprint's fieldtypes, their values will be saved in such a way that you may need augmentation to use them. For instance, the assets fieldtype will save an array of paths relative to the configured asset container, and when augmented will return an array of Asset objects. So, you may want to make sure that when you retrieve your data later, that it's [augmented](/extending/augmentation). ::: ## Blueprints In the examples above, we just said "get a blueprint" but didn't tell you _how_ to get a blueprint. There's a couple ways to do it: ### Get an actual user defined blueprint Get one from where all the blueprints are typically stored, by its handle. If it doesn't exist, it'll return `null`. ``` php use Statamic\Facades\Blueprint; Blueprint::find('example'); // resources/blueprints/example.yaml ``` ### Create one on the fly If you're wanting a blueprint just for sake of rendering this one specific form, you can create it in PHP. No YAML file necessary. Using the `makeFromFields` method, you can pass in an array of fields using the fieldset syntax: ``` php Blueprint::makeFromFields([ 'title' => [ 'type' => 'text', 'validate' => 'required', 'width' => 50, ], 'handle' => [ 'type' => 'text', 'validate' => 'required|alpha_dash', 'width' => 50, ], ]); ``` This will give you a blueprint with a single section (no tabs or sidebar). If you want to get fancy, you can `make` a Blueprint manually. The `setContents` method will expect an array in Blueprint syntax. ``` php Blueprint::make()->setContents([ 'sections' => [ 'main' => ['fields' => [ ['handle' => 'title', 'field' => ['type' => 'text']], ['handle' => 'content', 'field' => ['type' => 'markdown']], ]], 'sidebar' => ['fields' => [ ['handle' => 'slug', 'field' => ['type' => 'slug']], ]] ] ]); ``` ================================================ FILE: content/collections/pages/query-scopes-and-filters.md ================================================ --- title: 'Query Scopes & Filters' template: page updated_by: 42bb2659-2277-44da-a5ea-2f1eed146402 updated_at: 1569347415 intro: Query scopes and filters allow you to narrow down query results using custom conditions. id: 290e9a74-7c6b-4fd0-a90a-23f7ac38d0c5 --- You may create scopes that can be used in various places, such as inside the collection tag or inside control panel listings. ## Scopes Any scope classes located within `app/Scopes` will be automatically registered. You may create a scope class by running `php please make:scope YourScope`, which will give you a class with a few methods for you to implement, for example: ``` php where('featured', true); } } ``` The `apply` method will give you a query builder instance, allowing you to modify it how you see fit. It will also give you `$values`, which will be an array of contextual values. For example, when [using the scope on a collection tag](/tags/collection#custom-query-scopes), you will get all the parameter values. When used as a [filter](#filters) inside the control panel, you will get all of your filter's field values. Example: Suppose a collection named "portfolio" and a dynamically obtained "slug" variable. The scope is called PorfolioScope. In your Antlers template: ``` antlers {{ collection:portfolio query_scope="portfolio_scope" slug="{portfolio}" }} {{ content }} {{ /collection:portfolio }} ``` In `PortfolioScope`: ``` php public function apply($query, $values) { $slug = $values['slug']; $query->where('slug', $slug); } ``` This will gives you the content of that page. ### Using scopes programmatically In order to use a scope as a query builder method, like "local scopes" in Eloquent, you have to register it on the respective query builder: ```php use Statamic\Facades\Entry; public function boot() { Entry::allowQueryScope(Featured::class); } ``` ```php Entry::query()->where('this', 'that')->featured()->get(); ``` However, unlike Eloquent's local scopes, Statamic's scopes accept an array of context. Make sure to pass an associative array rather than individual arguments: ```php $query->featured([ 'field' => 'value', 'foo' => 'bar', ]); ``` ## Filters Filters are UI based [scopes](#scopes) that will be displayed in listings inside the Control Panel. You're able to configure any number of fields to a filter to allow your users to refine their listings. You may create a filter class by running `php please make:filter YourScope`, which will give you a class with a few methods for you to implement, for example: ``` php [ 'type' => 'radio', 'options' => [ 'featured' => __('Featured'), 'not_featured' => __('Not Featured'), ] ] ]; } public function autoApply() { return [ 'featured' => 'not_featured', ]; } public function apply($query, $values) { $query->where('featured', $values['featured'] === 'featured'); } public function badge($values) { return $values['featured'] === 'featured' ? __('is featured') : __('not featured'); } public function visibleTo($key) { return $key === 'entries' && $this->context['collection'] == 'blog'; } } ``` The `fieldItems` method lets you define which filter fields will be displayed, just like a field inside a Blueprint. The `apply` method works exactly as it would in a standard [scope](#scopes). The `badge` method lets you define the badge text to be used when the filter is active on a listing. The `visibleTo` method allows you to control in which listings this filter will be displayed. You will be given a key that represents the type of listing. For example, an author filter might be appropriate for the `entries` listing but not `users`. You may also be given an array of contextual data which will vary depending on the listing. For instance, for `entries`, the current collection name can be accessed with `$this->context['collection']`. The `autoApply` method lets you define a default value to apply. You may also pin your filters to the filters bar by setting the `$pinned` class property: ```php public $pinned = true; ``` ================================================ FILE: content/collections/pages/quick-start-guide.md ================================================ --- title: 'Quick start guide' intro: "A step-by-step guide to installing and building your first Statamic site." video: https://www.youtube.com/playlist?list=PLVZTm2PNrzMwYLGotkQvTvjsXAkANJIkc id: 1d1920fb-604c-4ac1-8c99-f0de44abc06b --- ## Overview Much of the documentation is intended to be used as a reference sheet for various features, explaining how they work and what options and settings they provide. But not this guide. This is for gluing it all together, assuming you know very little about how Statamic works. We'll only make a couple of assumptions here before we get started. 1. You are comfortable working with HTML. 2. You have a local dev environment with [composer](https://getcomposer.org/) installed. 3. You can copy and paste a few commands into the command line. 4. You have more than 5 minutes to spare. Let's enjoy ourselves here. ### What we're building We're going to build a simple personal website for a fictitious young aspiring programmer named Kurt Logan. Kurt always has and always will live in the 1980s and is very excited at the prospect of having his very own place in Cyberspace. **This is not a "5 minute quick install guide" – we're going to be building a simple yet full site from scratch so you can see how everything comes together. It will likely take around 20-30 minutes.** ## High level approach A high level approach to building a site in Statamic often looks like this. 1. Start with a static HTML site or series of different layouts 2. Break static files up into the appropriate [views](/antlers) (layouts, templates, and partials) 3. Create applicable [collections](/collections) to hold content and set up [routes](/routing) to determine your URL patterns 4. Stub out top level pages and map them to the proper templates 5. Configure [blueprints](/blueprints) to hold fields that match your HTML (like title, author, date, content) and move static content out of your markup and into entries using the beautiful UI 6. Keep going until your site is done Once familiar with Statamic, many developers begin building their static site right in Statamic, often blending all the steps into a smooth flowing river of productivity. ## Install Statamic Let's start right at the very beginning. Installing Statamic. There are a [few ways to do it](/installing), but we recommend using our CLI installer. So let's get that installed to your local machine. ``` shell composer global require statamic/cli ``` Now you can run the `statamic new` command wherever you prefer to keep your site projects (we use `~/Sites` but you do you) to get a fresh site up and running. ``` shell cd ~/Sites && statamic new cyberspace-place ``` You'll be asked a couple of questions, like whether you want to install a blank site or a [Starter Kit](/starter-kits) (to keep it really simple, start with a blank site), create your first user, or initialize a Git repository. Once the installer is done and if everything worked as expected, you should be able to visit [http://cyberspace-place.test](http://cyberspace-place.test) and see the Statamic welcome screen. If you encounter any errors, Google them frantically and try anything and everything suggested until it magically begins working. **Just kidding**, that's a terrible idea. Please don't do that. You should check our [troubleshooting](/troubleshooting) guide and [GitHub discussions](https://github.com/statamic/cms/discussions) to look for a validated solution before resorting to such measures. We try our best to have answers to all the most common things you might encounter. Modern web development is amazing when everything is up to date, and can be pretty frustrating when it isn't. We feel this pain too.
    Statamic Welcome Screen
    If you see this you are right on track.
    Next, in your command line navigate into the new site (`cd cyberspace-place`) and open the project directory in your code editor. We like [VS Code](https://code.visualstudio.com/) but there are a ton of great editors and IDEs out there. ## Signing Into the Control Panel As part of the install process, you should have created a super user account, but if you said no by accident, we've got your back. At any time you can run `php please make:user` from the command line and follow along with the prompts (name, email, etc). For the purpose of this walkthrough, be sure to say `yes` when asked if the user should be a **super user** otherwise you'll just have to do it again. And again. And again until you finally say `yes`. Never be afraid of committing to success.
    Statamic Make:User Command
    You can customize user fields later.
    Now you can sign in. Head to [http://cyberspace-place.test/cp](http://cyberspace-place.test/cp) and use your email address and password to sign into the control panel.
    Statamic Login Screen Statamic Login Screen
    If you see this screen at /cp you've just earned 200 XP!
    ## Make a home page Next, let's get some content of _our_ choosing to show on the homepage. Head to `Collections → Pages` in the control panel, and you'll see an empty home page entry waiting for you. Click on the entry's title to edit it. Type anything you want in the `content` field and then click **Save & Publish**.
    Editing the home page Editing the home page
    Don't overthink it. Just type some aedgaeduhadfubugra
    Note that the entry is using the `home` template (you can see it there in the `template` field). Let's edit it and reveal your new and incredible content to the browser. In your code editor, open the file `resources/views/home.antlers.html`. This is the home template. The "name" of a template is the filename _up until the file extension_. Any view ending in `.antlers.html` will be parsed with Statamic's [Antlers](/antlers) template parser. :::tip If a view file ends with `.blade.php` it will use Laravel's [Blade templates](/blade). This same pattern applies for any other template engine you may wish to install in the future, like Twig or something that hasn't been invented yet. ::: Delete all the placeholder HTML from the template and replace it with the following: ``` {{ content }} ``` Refresh the site in your browser and you should see your content in all of its glory. Each of those double curly tags is a **variable**. When on a URL that matches an entry's route rule, all of that entry's field data is available automatically in the defined template. We'll get into adding new fields in just a bit.
    Your new home page Your new home page
    What did you write? Was it a dad joke?
    ## Customize the Layout You probably noticed that there is some _very_ basic styling going on. That's coming from the **layout**. Time to customize that too. Open `resources/views/layout.antlers.html` and replace it with this: ``` {{ title }}
    {{ template_content }}
    ``` Your layout file contains any markup you want present no matter what page you’re on. It's usually the best place to put your `` meta markup, persistent site navigation, site footer, and other global things. Think of layouts like a **picture frame**, and everything that changes from section to section, page to page _inside_ the frame — goes into templates. In practice, templates are injected inline wherever you put a `{{ template_content }}` variable in your layout to create a complete HTML document.
    Your new layout
    If copy & pasted properly you should see this 👆
    ## Now let's build a blog You might have known it was coming next – it's the staple of every CMS walkthrough. How easy is it to build a blog? You're about to find out. But first, let's talk about what a blog is. A "blog" is a collection of posts that shares common traits or attributes. A typical blog post might contain a title, featured image, an author, a few tags, and the article content. There is always a list (sometimes called an "archive") of blog posts linking to each post's unique URL, and sometimes the homepage has a short list of the most recent posts as well. Let's detail exactly what we're going to build, and then build it. Here's our todo list: - Create a blog "Collection" with the following fields: `title` , `featured_image` , `author` , and `content` - Create a blog index page (`/blog`) - Create a blog detail page (`/blog/why-i-love-mustard`) - Add a list of the most recent 5 blog entries to the homepage ### Create a new collection Head back to the Control Panel and click on the Collections link in the sidebar. Click the blue **Create Collection** button and then call your new collection "Blog".
    Creating a blog collection Creating a blog collection
    Name it whatever you want, as long as you name it Blog.
    ## Scaffold your views Let's save you a minute or two and generate the index and show template. Click on **Scaffold Views**
    Link to Scaffold Views Link to Scaffold Views
    Click it.
    And then click the Create Views button. The defaults are perfect.
    Scaffold collection views Scaffold collection views
    Click the button.
    Two new files will be created. We'll be editing them soon: - `resources/views/blog/index.antlers.html` - `resources/views/blog/show.antlers.html` ## Configure the collection Next, let's configure the collection to behave the way a typical blog should. Click **Configure Collection**.
    Link to configure your collection Link to configure your collection
    And now click this.
    :::tip Statamic does its best to take a "start simple and add things as needed" approach to features and settings, in contrast to other platforms that take a "everything is included and rip out what you don't want" approach. This means that Statamic doesn't do everything right out the box, but is much simpler to customize how you want everything to work. ::: We'll review some of the important settings, but we only need to touch two of them to make a blog: - Enable Publish Dates (the subs-setting defaults are perfect) - Set your route rule
    Settings to make a blog Settings to make a blog
    These are the only two you need to set.
    By enabling **Publish Dates**, Statamic will add a date field to your list of available entry fields (called a Blueprint), and will use the specified date to determine whether a given entry should be visible or not. Typical blog posts with a date in the future would be a _scheduled_ post and not yet published, and one in the past is published, and therefore visible. This is how we'll configure our Blog Collection, and is the default behavior when you enable this feature. As you scroll you'll notice a **Content Model** section. That template you scaffolded in the previous step is automatically selected as the default template for new Blog entries. And finally in the **Routing & URLs** section you'll find the **Route** setting. Here you can create the URL pattern that all of your entries will follow. You can change this anytime and use any of the Collection's fields as variables in the pattern by surrounding them in single braces, `{like_this}`. Here are some common patterns you could choose from: | Example URL | Route Pattern Rule | |-----------------------------------|-----------------------------------| |`/blog/2021-12-24/merry-christmas` | `/blog/{year}-{month}-{day}/{slug}` | |`/blog/2020/still-bored` | `/blog/{year}/{slug}` | |`/blog/happy-new-year` | `/blog/{slug}` | | `/evergreen-syle` | `/{slug}` | :::tip Check out the full list of [available variables](/collections#meta-variables). Try saying "available variables" 3x fast. It's not the _best_ tongue-twister, but it does qualify. ::: When in doubt, keep it simple. And then save your changes. ## Creating your first entry We like to make things work and then make them better. With that in mind, let's make our first blog post and get it to show on the frontend before we configure all the custom fields and whatnot. Head back to your blog Collection screen and click **Create Entry**.
    Link to create your first blog entry Link to create your first blog entry
    And finally, click this.
    Now you can see all the default fields for your new Collection. They're the same as the Home entry you edited a few moments ago. Go ahead and make a new blog post. Make two if you'd like! It's up to you. | Field | Notes | |-----------------------------------|-----------------------------------| | **Title** | The required title of the entry | | **Content** | A simple [Markdown](/fieldtypes/markdown) field | | **Author** | Defaults to whoever is logged in | | **Template** | When not _explicitly set_ will use the Collection's default | | **Slug** | Automatically generated off the title until you edit it manually | | **Date** | Defaults to today | ## Time for more frontend It's code editor time! Let's get that list of the 5 most recent entries onto the homepage since it already exists and is one of our todos. Open `resources/views/home.antlers.html` and replace that lonely `{{ content }}` with this markup (don't worry, we'll explain what's going on in a moment): ``` // resources/views/home.antlers.html

    Welcome to my CyberSpace Place!

    {{ content }}

    Recent Blog Posts

    {{ collection:blog limit="5" }} {{ title }} {{ date }} {{ /collection:blog }}
    ``` If you refresh your homepage (and managed to name your placeholder entry or two the same as us), you should see this:
    Link to create your first blog entry
    We said it would look ugly, but we lied.
    Let's take a closer look at how this works. Stripping out all the styling in the example, here's the most basic [Antlers](/antlers) template snippet that fetches your entries. ``` {{ collection:blog limit="5" }} {{ title }} {{ /collection:blog }} ``` Here you can see we're telling the [Collection Tag](/tags/collection) tag to use the `blog` collection and limit the number of returned entries to 5. Inside the tag pair is a loop that iterates over each entry with access to all the data available as `{{ variables }}`. The `url` will follow the pattern you set in the route rule (`/blog/hello-from-cyberspace` perhaps?) and if you were to click it, you'd see a new page using the `resources/views/blog/show.antlers.html` template, which is empty so there's not much to look at. Let's edit that next. ## The blog "show" template Now that we're on an entry's very own unique URL, you no longer need that `{{ collection:blog }}` tag pair to fetch data. All of the entry's data is available automatically. Here's a really simple snippet you can drop in so you can see the data pull through. ``` // resources/views/blog/show.antlers.html

    {{ title }}

    Published on {{ date }} by {{ author:name }}
    {{ content }}
    ``` A few cool things to note here in this code example: - The author's `name` is being accessed by reaching into the `{{ author }}` object. You can retrieve any data (but not password) on a user this way. Pretty cool. - The `content` field is being automatically converted from Markdown to HTML because we're using a [Markdown](/fieldtypes/markdown) field. If you were to use a generic [Textarea](/fieldtypes/textarea) field, you'd need to transform the Markdown yourself by using a [modifier](/modifiers). It would look like this: `{{ textarea | markdown }}`.
    A blog post
    How close does yours look?
    ## Blog index Next, let's make that blog index page. Head back to the control panel and go to the **Pages** collection. Create a new entry and call it "Blog", "My Blog", or even "My CyberBlog" — just make sure the slug is `blog`. Set the template to `blog/index`. Back to your code editor — open up the `resources/views/blog/index.antlers.html` template and drop in this snippet. It's essentially what we built on the home page, but without the limit. ``` // resources/views/blog/index.antlers.html

    {{ title }}

    {{ content }}
    {{ collection:blog }} {{ title }} {{ date }} {{ /collection:blog }}
    ``` And stop right there. We've now duplicated a whole chunk of code for one tiny little bit — `limit="5'`. Let's DRY this up (reduce code duplication). :::tip It's totally fine to duplicate code sometimes, especially if you have to make some code significantly more complex to reuse it. Just keep that in mind. We'll keep this simple. ::: ## Your first partial Partials are reusable template chunks. Create a new file named `_listing.antlers.html` in the `resources/views/blog/` directory. Prefixing a template with an underscore is a common convention to indicate that it's a reusable partial and not a full layout. You could also create a subdirectory named `partials` — it's up to you. Just be consistent. Inside that new template file, copy and paste the entire `
    ` chunk that includes the Collection tag pair from either the homepage, the blog index, or this guide. We can create a variable on the fly here so you can pass a desired limit into your partial. Replace that second line with this: ``` {{ collection:blog :limit="limit" }} ``` Prefixing the `limit` parameter with a colon tells Statamic to look for a variable named "limit" as the argument. If there isn't one it will be `null`, which will not set a limit which is how we want it on the blog index template. Your blog index template can now look like as simple as this: ``` // resources/views/blog/index.antlers.html

    {{ title }}

    {{ content }} {{ partial:blog/listing }} ``` Now let's dry up the home template. We know we need to pass that limit in, but if you recall (or visit the homepage), we had that extra `

    ` above the `collection:blog` tag. This is a perfect opportunity to add a "slot". Switch to your new `blog/listing` partial and add `{{ slot }}` to the line right above the collection tag, like so: ``` // resources/views/blog/_listing.antlers.html
    {{ slot }} {{ collection:blog :limit="limit" }} ... ``` Back in your `home` template, you can now replace that chunk of markup with a call to the partial, setting the limit, and using it as a tag pair to send the contents in as the `slot`. A super helpful little pattern. Here's your entire home template: ``` // resources/views/home.antlers.html

    Welcome to my CyberSpace Place!

    {{ content }} {{ partial:blog/listing limit="5" }}

    Recent Blog Posts

    {{ /partial:blog/listing }} ``` ## The nav We're almost done, but before we head back to the control panel to add a few more fields to your blog blueprint, let's add a nav. Your `home` and `blog` entries are both in an "ordered" Pages collection. If you look at this default collection's config you'll see that it has the **Orderable** setting on and that the root page is considered the home page. This lets you have a page with a slug of `/`. We can use the [Nav tag](/tags/nav) to fetch the entries in the Pages collection in the order you have them arranged. Open up your layout file and drop in this nav snippet, right after the open body tag. ``` // resources/views/layout.antlers.html // ... ``` The nav tag works very much like the `collections` tag. It loops through the entries and gives you access to all the data inside each. ## Customizing your blueprint We've got a pretty functional site going here, but so far we've only worked with default fields. Few sites can be so simple, so let's spice it up a bit. Head to the **Blueprints** area in the sidebar and click **Blog**. Now you're looking at all the fields you've been working with, organized into Tab Sections. Tab Sections let you group fields into Tabs which can help you stay organized, keep similar fields together, or help push optional, unusual fields out of mind for most authors. It's up to you how you'd like to organize these.
    A Blueprint and its default fields A Blueprint and its default fields
    This is content modeling right here.
    You can drag, drop, and rearrange fields inside and across your sections. This order will be how you see the fields in the publish screen. :::tip **The Sidebar** is a special section. It controls the fields shown in the publish sidebar when your browser is wide enough, and collapses those fields to a tab when it isn't. If you delete the Sidebar section, you won't have one — and if you create a new one called "Sidebar", it'll work just as before. ::: Let's create a new field called `featured_image`. Click **Create Field** in the **Main** section and behold! A big list of fieldtypes! You can learn more about [each Fieldtype](/fieldtypes) elsewhere in the docs, but here are a few quick tips on narrowing down what you're looking for. When this screen is opened, you're automatically focused in the search box, so you can start typing the fieldtype name if you know it (Hint: you could type `assets` now). Or, you can narrow the fields down by type – All, Text, Media, and Relationship. You'd find the Assets fieldtype inside Media.
    A list of Statamic's fieldtypes A list of Statamic's fieldtypes
    Over 40 different types to pick from!
    Find the **Assets** fieldtype and click it. Assets fields let you pick from and upload new files. Next, give the field the `Display` name "Featured Image" and you'll see the `Handle` get slugified automatically to `featured_image`. This will be the variable name you will use in your templates to get the asset's data. The only additional setting you should tweak for now is to set `Max Files` to `1`. When you're done, click **Finish**.
    Configuring an Assets fieldtype Configuring an Assets fieldtype
    Every fieldtype has shared & unique options.
    Head back to your Blog collection and edit an entry (or create a new one if you'd like). You'll see your new field right there. Upload any image you have on your computer. If you need a dummy image, we recommend Google Image Searching for "rad 90s aesthetic". That's a gold mine right there. Hover over the thumbnail for your new image and click the Edit button (it looks like a pencil). There you can make a few adjustments to the image – like setting an Alt tag.
    Adding an Alt tag to an image Adding an Alt tag to an image
    Seeeegaaaahh!
    :::tip Assets can have Blueprints too! ::: When you're done, **Save & Publish** your changes. ## Wiring up the new field Head back to `resources/views/blog/show.antlers.html` in your code editor. Add the following snippet anywhere you'd like in the template. Either before or after the `{{ content }}` variable is probably a good place. ``` // resources/views/blog/show.antlers.html // ... {{ featured_image:alt }} ``` Refresh the page and there you have it — a basic but fully functional website. Hopefully you'll have a better idea how the basics fit together, as well as the relationship between the control panel and the frontend. There are so many more things you can do – like add [Taxonomies](/taxonomies), [Forms](/forms), [dynamic image manipulations](/tags/glide), fetch data with JavaScript with our [Content API](/rest-api) and on and on. And make sure to not miss the list of [Tags](/tags) and [Modifiers](/modifiers) that do all sorts of powerful things in your templates. ## Going deeper We have a screencast series that covers getting started but goes much further and deeper. Feel free to [check that out here](https://www.youtube.com/playlist?list=PLVZTm2PNrzMwYLGotkQvTvjsXAkANJIkc). Good luck! ================================================ FILE: content/collections/pages/relationship-fieldtypes.md ================================================ --- title: 'Relationship Fieldtypes' template: page updated_by: 42bb2659-2277-44da-a5ea-2f1eed146402 updated_at: 1569347303 intro: The Relationship fieldtype is one of the more powerful fields in Statamic's core. So powerful, in fact, that it earns its very own page in the docs. This is that page. id: 06813e5d-158e-4318-aa4a-b29fd87d107f --- By default, the relationship fieldtype lets you select entries from various collections as well as create and edit items on the fly from _within_ the field. You can create your own relationship fields that provide the ability to select all different sorts of items from anywhere. ## Example To illustrate that you can get items from anywhere — even remote APIs — we'll build a field where you can select GitHub repositories for a given user. In your blueprints, you'll be able to use `type: repos` (whatever you name your fieldtype) and all the options that the relationship field would normally give you, like `max_items`: ``` yaml fields: handle: repos field: type: repos max_items: 3 ``` ## Creating the Fieldtype You will need to create the fieldtype – no Vue component necessary – so you can skip it with the `--php` flag: ``` shell php please make:fieldtype Repos --php ``` Then instead of extending `Fieldtype`, you'll extend the existing `Relationship` fieldtype: ``` php use Statamic\Fieldtypes\Relationship; class Repos extends Relationship { // } ``` There are a handful of methods and properties inside the `Relationship` class, and you can override them to control how it functions. There are three main areas you will want to customize. The index items, the selected item data, and the listing data. ## Index Items The index items are what you'll see in the item selector stack. You can either override the `getIndexQuery` method if you're dealing with items being retrieved through the Statamic API. You'll need to return a QueryBuilder. ``` php public function getIndexQuery($request) { return Entry::query()->whereIn('collection', $request->collections); } ``` Or, you can override `getIndexItems` for full control. We'll use this for our GitHub example. ``` php use Carbon\Carbon; use Illuminate\Support\Facades\Http; public function getIndexItems($request) { $repos = Http::github() ->get("/users/{$this->config('username')}/repos") ->json(); return $this->formatRepos($repos); } protected function formatRepos($repos) { return collect($repos)->map(function ($repo) { $updated = Carbon::parse($repo['updated_at']); return [ 'id' => $repo['id'], 'name' => $repo['name'], 'description' => $repo['description'], 'stars' => $repo['stargazers_count'], 'updated' => $updated->timestamp, 'updated_relative' => $updated->diffForHumans(), 'owner' => $repo['owner']['login'], ]; }); } ``` You can customize which columns will be used in the selector by overriding the `getColumns` method: ``` php use Statamic\CP\Column; protected function getColumns() { return [ Column::make('name'), Column::make('description'), Column::make('stars'), Column::make('updated')->value('updated_relative'), ]; } ``` ## Selected Item Data Once you select items, their `id` values will be used as the value for your field. If you were to hit save, you would see something like this in your content files: ``` yaml repos: - 54376134 - 89473529 ``` In order to convert those values into something useful, you'll either need to override the `getItemData` method or the `toItemArray` method. For our example, we'll use the former: ``` php public function getItemData($values) { $repos = collect($values)->map(fn ($id) => Http::github()->get("/repositories/{$id}")->json()); return $this->formatRepos($repos); } ``` ### Listing Data When field data is to be displayed in a listing view (eg. in the entries listing table or the entry fieldtype), you may customize the display by overwriting the `preProcessIndex` method. In our GitHub field, let's show only the repo names: ```php public function preProcessIndex($data) { return collect($data) ->map(fn ($id) => Http::github()->get("/repositories/{$id}")->json('name')) ->join(', '); } ``` ## Hints Hints are short bits of secondary text shown next to selected items and dropdown options. They're useful when multiple items could share the same title and you need a way to disambiguate them — like an entry titled "About" that could exist in several collections. Override the `getItemHint` method to return a string (or `null` for no hint): ``` php public function getItemHint($item): ?string { return $item['owner']; } ``` The built-in `Entries` and `Terms` fieldtypes use this to show the collection or taxonomy title when more than one is configured. ## Creating Items To disable creation of items, you can add the canCreate property. ``` php protected $canCreate = false; ``` ## Searching By default, the search bar will be visible in the selector stack. When a user types into it, its value will be submitted in the `search` query parameter. You can tweak your logic to account for searching in your `getIndexItems` method. For example: ``` php public function getIndexItems($request) { return $request->search ? $this->searchRepos($request->search) : $this->userRepos(); } ``` To disable searching, you can add the canSearch property. ``` php protected $canSearch = false; ``` ## Customizing the view By default, the fieldtype will show the standard draggable block, with the `title` as the text. You may provide your own Vue component to the `itemComponent` property to replace it. ``` php protected $itemComponent = 'GithubRepoRelationshipItem'; ``` ``` js import GithubRepoRelationshipItem from './GithubRepoRelationshipItem.vue'; Statamic.$components.register('GithubRepoRelationshipItem', GithubRepoRelationshipItem); ``` ``` vue ``` An `item` prop will be passed to your component which will contain one the objects provided by the `getItemData` method. In order to allow your users to remove their selection, you should emit a `removed` event, as shown above. ================================================ FILE: content/collections/pages/relationships.md ================================================ --- id: 8ed04215-9f46-4000-bd67-c71b21b67d85 blueprint: page title: Relationships template: page intro: 'Content is often related to other content and bits of data. A blog post may have an author and 3 other recommended posts. A product may have a brand and a category. A hot dog may have a bun and some mustard. This page covers ways to create and take advantage of these types of relationships.' related_entries: - d0c65546-74f1-4a15-89d5-1562a95ee2c6 - acee879a-c832-449d-a714-c57ea5862717 - 31adcc00-4fbb-4fe9-9b48-401061273096 - 0f8102b9-c948-4264-8cb8-cbfbd0415a04 --- ## Overview Statamic relationships are defined by storing an `id` or `handle` of one piece of content (an entry, term, or user for example) in a variable on another piece of content. Once linked in this simple-but-specific manner, you can fetch and display the related content by using the variable in your templates. ## Fieldtypes There are 13 fieldtypes that manage relationships in one fashion or another. When you use these fieldtypes in your [blueprint](/blueprints), the relationships are automatically resolved on the front-end of your site and you can work directly with the data it references. - [Assets](/fieldtypes/assets) - [Collections](/fieldtypes/collections) - [Entries](/fieldtypes/entries) - [Form](/fieldtypes/form) - [Link](/fieldtypes/link) - [Navs](/fieldtypes/navs) - [Sites](/fieldtypes/sites) - [Structures](/fieldtypes/structures) - [Taxonomies](/fieldtypes/taxonomies) - [Taxonomy Terms](/fieldtypes/terms) - [User Groups](/fieldtypes/user-groups) - [User Roles](/fieldtypes/user-roles) - [Users](/fieldtypes/users) ## Example Let's use this example product entry to walk through displaying data from the three relationships: photo, author, and related products. ``` yaml # /content/products/wayne-gretzky-pog-collection.md title: Wayne Gretzky Pog Collection price: 2495.00 template: products.show id: 123-1234-12-4321 photo: products/gretzky-pogs-FINAL-(2).jpg author: abc-abcd-ab-dcba related_products: - 789-7890-78-0987 - abc-1234-bc-4eba ``` ### Field breakdown - `id` is the unique identifier given to this particular entry - `photo` is a reference to an asset image of the product (why didn't they clean up the filename?) - `author` is the id of the user who created this entry - `related_products` is an array of other product entry ids ### Templating In this following template example you can see how easy it is to use the data from related entries, assets, and users. You don't need to write queries, request data filter results, or anything complicated. As long as you've used the appropriate [fieldtypes](#fieldtypes) in your [blueprint](/blueprints), the data will be ready and waiting for you to use in your view template. ::tabs ::tab antlers ```antlers

    {{ title }}

    ${{ price }}

    {{ alt }}

    Listed by: {{ author:name }}

    Related Products

    {{ related_products }}
    {{ title }}
    {{ price }}
    {{ alt }}
    {{ /related_products }}
    ``` ::tab blade ```blade

    {{ $title }}

    ${{ $price }}

    {{ $photo->alt }}

    Listed by: {{ $author->name }}

    Related Products

    @foreach ($related_products as $product)
    {{ $product->title }}
    {{ $product->price }}
    {{ $product->photo->alt }}
    @endforeach
    ``` :: ## Manual fetching If you _aren't_ using a relationship fieldtype but _do_ have an `id` or `handle` to fetch data from you can use the [get_content tag](/tags/get_content). ::tabs ::tab antlers ```antlers {{ get_content from="123-1234-12-4321" }} {{ title }} {{ /get_content }} {{ get_content :from="related_id" }} {{ title }} {{ /get_content }} ``` ::tab blade ```blade {{ $title }} {{ $title }} ``` :: ================================================ FILE: content/collections/pages/release-schedule-support-policy.md ================================================ --- id: c9ee871c-002b-48d7-b845-1abac58de337 blueprint: page title: 'Release Schedule & Support Policy' nav_title: 'Release Schedule' intro: 'For all Statamic releases, bug fixes are provided for 1 year and security fixes are provided for 18 months. For all first party addons, only the latest major release receives bug fixes. In addition, please review the [Laravel Support Policy](https://laravel.com/docs/master/releases#support-policy).' --- ## Versioning scheme Statamic and its other first-party packages follow [Semantic Versioning](https://semver.org/). Major releases are released every year (~Q1). Minor and patch releases may be released as often as every few days. Minor and patch releases should never contain breaking changes. ## Support policy
    Statamic Laravel PHP Release Bug Fixes Until Security Fixes Until
    3.4* 8-9 7.4-8.1 Jan 2023 Jan 2023 Jul 2024
    4 9-10 8.0-8.3 Mar 2023 May 2024 Sep 2024
    5 10-12 8.2-8.4 May 2024 Mar 2026 Dec 2026
    6 12-13 8.3-8.5 Jan 2026 Mar 2027 Dec 2027
    7 13-14 8.4-8.6 Q1 2027 TBC TBC
    _*Prior to Semantic Versioning_ These dates are subject to changes based on factors outside of our control, such as the release schedule and required versions of Laravel and major Laravel packages we depend upon. ================================================ FILE: content/collections/pages/repositories.md ================================================ --- title: Repositories template: page updated_by: 42bb2659-2277-44da-a5ea-2f1eed146402 updated_at: 1569347424 intro: Statamic uses a repository pattern to retrieve data from various places. id: c3da9537-5d5f-4b84-a4be-882b89217151 --- For example, when you call `Entry::whereCollection('blog')`, it asks "the entry repository" to get the blog entries instead of immediately assuming the entries will be located in a blog directory on the filesystem. Out of the box, Statamic will typically use an implementation that gets data from the "Stache", which is our file-backed database. ## Custom Repositories Let's say you want to store your entries in a database. You would need to swap the default entry repository (which gets entries from the Stache) with your own that would get entries from a database. In a service provider's `register` you can re-bind the contract using the `Statamic::repository()` method: ``` php DatabaseEntry::class, Statamic\Contracts\Entries\QueryBuilder::class => DatabaseEntryQueryBuilder::class, ]; } ``` Alternatively, if you want to use a custom item class without customizing the entire repository, you're free to just re-bind that class in your service provider's `boot` method (so it's re-bound after everything else). This could be more useful to you if you just need to customize a method or two. ``` php class AppServiceProvider extends ServiceProvider { public function boot() { $this->app->bind( Statamic\Contracts\Entries\Entry::class, CustomEntry::class ); } } ``` :::tip Make sure to clear your cache after changing a binding like this. ::: ================================================ FILE: content/collections/pages/requirements.md ================================================ --- title: Requirements intro: Statamic is a modern PHP application built as a [Laravel](https://laravel.com) package, which carries with it the same [server requirements](https://laravel.com/docs/13.x/deployment#server-requirements) as Laravel itself. To manipulate images (resize, crop, etc), you will also need the GD Library or ImageMagick installed on your server. template: page id: 792644d2-8bd2-421d-a080-e0be7fca125c blueprint: page --- ## Server requirements To run Statamic you'll need a server meeting the following requirements. These are standard defaults (at minimum) for most modern hosting platforms. - PHP 8.3 or above - BCMath PHP Extension - Ctype PHP Extension - Exif PHP Extension - JSON PHP Extension - Mbstring PHP Extension - OpenSSL PHP Extension - PDO PHP Extension - Tokenizer PHP Extension - XML PHP Extension - GD Library or ImageMagick - Composer ## Development environments Depending on your operating system, we recommend the following development environments: ### macOS and Windows: Laravel Herd [Laravel Herd](https://herd.laravel.com) is a blazing fast, native development environment for macOS and Windows. Herd includes `php`, `composer` and `npm` - *almost* everything you need to setup Statamic locally. We've written [a guide](/installing/laravel-herd) on installing Herd and setting up your Statamic site. ### Linux To develop locally with Statamic on Linux, you'll need to install `php`, `composer` and `npm`. If you're using Ubuntu (or another variant of Debian), you may find our [Ubuntu guide](/installing/ubuntu) helpful. ## Recommended hosts We recommend using [Digital Ocean](https://m.do.co/c/6469827e2269) to host most small to medium Statamic sites. Their servers are fast, inexpensive, and we use them ourselves. _**Full disclosure:** that's an affiliate link but we wouldn't recommend them if it wasn't an excellent option._ Some developers choose to pair Digital Ocean with a tool like [Laravel Forge](/deploying/laravel-forge) or [Ploi](/deploying/ploi), which help you provision servers and handle deployments. However, if you're comfortable doing that yourself, then feel free! We also maintain a user-contributed [Github repo](https://github.com/statamic/hosts) full of other host recommendations. ================================================ FILE: content/collections/pages/resource-apis.md ================================================ --- id: 50dfaf8c-8ca4-4b58-ac84-e4c50099e42e blueprint: page title: 'Resource APIs' template: repositories/index --- ================================================ FILE: content/collections/pages/rest-api.md ================================================ --- id: 2e0d2f8f-319d-4cce-bd90-16d6ad32ad37 blueprint: page title: 'REST API' intro: 'The Content REST API is a **read-only** API for delivering content from Statamic to your frontend, external apps, SPAs, and numerous other possible sources. Content is delivered as JSON data.' pro: true --- (If you're interested in [GraphQL](/graphql), we have that too.) ## Enable the API To enable the REST API, add the following to your `.env` file: ```env STATAMIC_API_ENABLED=true ``` Or you can enable it for all environments in `config/statamic/api.php`: ```php 'enabled' => true, ``` You will also need to [enable the resources](#enable-resources) you want to be available. For security, they're all disabled by default. ### Enable resources You can enable resources (ie. Collections, Taxonomies, etc.) in your `config/statamic/api.php` config: ```php 'resources' => [ 'collections' => true, 'taxonomies' => true, // etc ] ``` ### Enable specific sub-resources If you want more granular control over which sub-resources are enabled within a resource type (ie. enabling specific Collection queries only), you can use array syntax: ```php 'resources' => [ 'collections' => [ 'articles' => true, 'pages' => true, // 'events' => false, // Sub-resources are disabled by default ], 'taxonomies' => true, // etc. ] ``` ## Endpoints ` https://yourdomain.tld/api/{endpoint} ` You may send requests to the following endpoints: - [Entries](#entries) / [Entry](#entry) - [Collection Tree](#collection-tree) / [Navigation Tree](#navigation-tree) - [Taxonomy Terms](#taxonomy-terms) / [Taxonomy Term](#taxonomy-term) - [Assets](#assets) / [Asset](#asset) - [Globals](#globals) / [Global](#global) - [Forms](#forms) / [Form](#form) - [Users](#users) / [User](#user) ### Customizing the API URL You may customize the route in your API config file or with an environment variable. ```php // config/statamic/api.php 'route' => 'not_api', ``` ```env STATAMIC_API_ROUTE=not_api ``` ## Filtering ### Enabling filters For security, [filtering](#filtering) is disabled by default. To enable, you'll need to opt in by defining a list of `allowed_filters` for each sub-resource in your `config/statamic/api.php` config: ```php 'resources' => [ 'collections' => [ 'articles' => [ 'allowed_filters' => ['title', 'status'], ], 'pages' => [ 'allowed_filters' => ['title'], ], 'events' => true, // Enable this collection without filters 'products' => true, // Enable this collection without filters ], 'taxonomies' => [ 'topics' => [ 'allowed_filters' => ['slug'], ], 'tags' => true, // Enable this taxonomy without filters ], // etc. ], ``` For endpoints that don't have sub-resources (ie. users), you can define `allowed_filters` at the top level of that resource config: ```php 'resources' => [ 'users' => [ 'allowed_filters' => ['name', 'email'], ], ], ``` ### Using filters You may filter results by using the `filter` query parameter. ``` url /endpoint?filter[{field}:{condition}]={value} ``` You may use the [conditions](/conditions) available to the collection tag. eg. `contains`, `is`, `isnt` (or `not`), etc. For example: ``` url /endpoint?filter[title:contains]=awesome&filter[featured]=true ``` This would filter down the results to where the `title` value contains the string `"awesome"`, and the `featured` value is `true`. When you omit the condition, it defaults to `is`. ### Advanced filtering config You can also allow filters on all enabled sub-resources using a `*` wildcard config. For example, here we'll enable only the `articles`, `pages`, and `products` collections, with `title` filtering enabled on each, in addition to `status` filtering on the `articles` collection specifically: ```php 'resources' => [ 'collections' => [ '*' => [ 'allowed_filters' => ['title'], // Enabled for all collections ], 'articles' => [ 'allowed_filters' => ['status'], // Also enable on articles ], 'pages' => true, 'products' => true, ], ], ``` If you've enabled filters using the `*` wildcard config, you can disable filters on a specific sub-resource by setting `allowed_filters` to `false`: ```php 'resources' => [ 'collections' => [ '*' => [ 'allowed_filters' => ['title'], // Enabled for all collections ], 'articles' => [ 'allowed_filters' => false, // Disable filters on articles ], 'pages' => true, 'products' => true, ], ], ``` Or you can enable endpoints and filters on all sub-resources at once by setting both `enabled` and `allowed_filters` within your `*` wildcard config: ```php 'resources' => [ 'collections' => [ '*' => [ 'enabled' => true, // All collection endpoints enabled 'allowed_filters' => ['title'], // With filters enabled for all ], ], ], ``` ## Sorting You may sort results by using the `sort` query parameter: ``` url /endpoint?sort=field ``` You can sort in reverse by prefixing the field with a `-`: ``` url /endpoint?sort=-field ``` You may sort by multiple fields by comma separating them. The reverse flag can be combined with any field: ``` url /endpoint?sort=one,-two,three ``` You can sort nested fields using the `->` operator, like this: ```url /endpoint?sort=nested->field ``` ## Selecting fields You may specify which top level fields should be included in the response. ``` url /endpoint?fields=id,title,content ``` ## Pagination Results will be paginated into 25 items per page by default. You may specify the items per page and which page you are viewing with the `limit` and `page` parameters: ``` url /endpoint?limit=10&page=1 ``` The response will contain your `data`, `links` to easily get next/previous URLs, and `meta` information for more easily creating a paginator. ``` json { "data": [ {...}, {...}, ], "links": { "first": "/endpoint?limit=10&page=1", "last": "/endpoint?limit=10&page=3", "prev": null, "next": "/endpoint?limit=10&page=2", }, "meta": { "current_page": 1, "from": 1, "to": 10, "total": 29, "per_page": 10, "path": "/endpoint", } } ``` --- ## Entries `GET` `/api/collections/{collection}/entries` Gets entries within a collection. ``` json { "data": [ { "title": "My First Day" } ], "links": {...}, "meta": {...} } ``` :::tip If you are using [Multi-Site](/multi-site), the entries endpoint will serve from all sites at once. If needed, you can limit the fetched data to a specific site with a `site` [filter](#filtering) (ie. `&filter[site]=fr`). ::: ## Entry `GET` `/api/collections/{collection}/entries/{id}` Gets a single entry. ``` json { "data": { "title": "My First Day" } } ``` ## Collection Tree `GET` `/api/collections/{collection}/tree` Gets entry tree for a structured collection. ``` json { "data": [ { "page": { "title": "About", "url": "/about" }, "depth": 1, "children": [ { "page": { "title": "Articles", "url": "/about/articles" }, "depth": 2, "children": [] } ] } ] } ``` ### Params On this endpoint, the [fields](#selecting-fields) param will allow you to select fields within each `page` object. You may also set a `max_depth` to limit nesting depth, or `site` to choose the site. ```url /api/collections/{collection}/tree?fields=title,url&max_depth=2&site=fr ``` ## Navigation Tree `GET` `/api/navs/{nav}/tree` Gets tree for a navigation structure. ``` json { "data": [ { "page": { "title": "Recommended Products", "url": "https://rainforest.store/?cid=statamic", }, "depth": 1, "children": [ { "page": { "title": "Books", "url": "https://rainforest.store/?cid=statamic&type=books", }, "depth": 2, "children": [] } ] } ] } ``` ### Params On this endpoint, the [fields](#selecting-fields) param will allow you to select fields within each `page` object. You may also set a `max_depth` to limit nesting depth, or `site` to choose the site. ```url /api/navs/{nav}/tree?fields=title,url&max_depth=2&site=fr ``` ## Taxonomy Terms `GET` `/api/taxonomies/{taxonomy}/terms` Gets terms in a taxonomy. ``` json { "data": [ { "title": "Music", } ], "links": {...}, "meta": {...} } ``` :::tip If you are using [Multi-Site](/multi-site), you can select the site using a `site` [filter](#filtering) (ie. `&filter[site]=fr`). ::: ## Taxonomy term `GET` `/api/taxonomies/{taxonomy}/terms/{slug}` Gets a single taxonomy term. ``` json { "data": { "title": "My First Day" } } ``` ## Globals `GET` `/api/globals` Gets all globals. ``` json { "data": [ { "handle": "global", "api_url": "http://example.com/api/globals/global", "foo": "bar", }, { "handle": "another", "api_url": "http://example.com/api/globals/another", "baz": "qux", } ], } ``` :::tip If you are using [Multi-Site](/multi-site), you can select the site using the `site` parameter (ie. `&site=fr`). ::: ## Global `GET` `/api/globals/{handle}` Gets a single global set's variables. ``` json { "data": { "handle": "global", "api_url": "http://example.com/api/globals/global", "foo": "bar", } } ``` ## Forms `GET` `/api/forms` Gets all forms. ``` json { "data": [ { "handle": "contact", "title": "Contact", "fields": { "name": {...}, "email": {...}, "inquiry": {...} }, "api_url": "http://example.com/api/forms/contact", }, { "handle": "newsletter", "title": "Subscribe to Newsletter", "fields": { "email": {...} }, "api_url": "http://example.com/api/forms/newsletter", } ], } ``` ## Form `GET` `/api/forms/{handle}` Gets a single form. ``` json { "data": { "handle": "contact", "title": "Contact", "fields": { "name": {...}, "email": {...}, "inquiry": {...} }, "api_url": "http://example.com/api/forms/contact", } } ``` ## Users `GET` `/api/users` Get users. ``` json { "data": [ { "id": "1", "email": "john@smith.com", "api_url": "http://example.com/api/users/1" } ], "links": {...}, "meta": {...} } ``` ## User `GET` `/api/users/{id}` Get a single user. ``` json { "data": { "id": "1", "email": "john@smith.com", "api_url": "http://example.com/api/users/1" } } ``` ## Assets `GET` `/api/assets/{container}` Get a container's asset data. ``` json { "data": [ { "id": "main::foo.jpg", "url": "/assets/foo.jpg", "api_url": "http://example.com/api/assets/main/foo.jpg", "alt": "A picture of nothing." } ], "links": {...}, "meta": {...} } ``` ## Asset `GET` `/api/assets/{container}/{path}` Get a single asset's data. The `path` in the URL should be the relative path from the container's root. ``` json { "data": { "id": "main::foo.jpg", "url": "/assets/foo.jpg", "api_url": "http://example.com/api/assets/main/foo.jpg", "alt": "A picture of nothing." } } ``` ## Customizing resources By default, the resources generally use the item's [Augmented](/augmentation) data. You are free to override the resource classes with your own, in turn letting you customize the responses. In a service provider, use the `map` method to define the overriding resources: ``` php use App\Http\Resources\CustomEntryResource; use Statamic\Http\Resources\API\Resource; use Statamic\Http\Resources\API\EntryResource; class AppServiceProvider extends Provider { public function boot() { Resource::map([ EntryResource::class => CustomEntryResource::class, ]); } } ``` ``` php $this->resource->id(), 'title' => $this->resource->value('title'), ]; } } ``` ## Caching API responses are cached by default. You may customize the cache expiry in `config/statamic/api.php`. ```php 'cache' => [ 'expiry' => 60, ], ``` ### Cache invalidation Cached responses are automatically invalidated when content is changed. Depending on your API usage and blueprint schema, you may also wish to ignore specific events when invalidating. ```php 'cache' => [ 'expiry' => 60, 'ignored_events' => [ \Statamic\Events\UserSaved::class, \Statamic\Events\UserDeleted::class, ], ], ``` ### Disabling caching If you wish to disable caching altogether, set `cache` to `false`. ```php 'cache' => false, ``` ### Custom cache driver If you need a more intricate caching solution, you may reference a custom cache driver class and pass extra config along if necessary. ```php 'cache' => [ 'class' => CustomCacher::class, 'expiry' => 60, 'foo' => 'bar', ], ``` Be sure to extend `Statamic\API\AbstractCacher` and implement the required methods. You can access custom config via the `config()` method, ie. `$this->config('foo')`. ```php use Statamic\API\AbstractCacher; class CustomCacher extends AbstractCacher { public function get(Request $request) { // } public function put(Request $request, JsonResponse $response) { // } public function handleInvalidationEvent(Event $event) { // } } ``` ## Rate limiting The REST API is rate limited to **60 requests per minute** by default. You can change this configuration in your `RouteServiceProvider`. Learn more about [rate limits in Laravel](https://laravel.com/docs/master/rate-limiting). ```php // app/Providers/RouteServiceProvider.php protected function configureRateLimiting() { RateLimiter::for('api', function (Request $request) { return Limit::perMinute(60); }); } ``` ## Authentication Out of the box, the REST API is publicly accessible. You can restrict access to the API by adding the `STATAMIC_API_AUTH_TOKEN` key to your `.env` file. It should be set to a long, random string. ```php STATAMIC_API_AUTH_TOKEN=a-long-random-string ``` Then, when you make requests to the REST API, you'll need to include the token in the `Authorization` header, like this: ```curl curl -X GET "https://example.com/api/collections/pages/entries" \ -H "Authorization: Bearer a-long-random-string" \ -H "Accept: application/json" ``` ### Authenticating users If you want to authenticate based on users, we recommend using [Laravel Sanctum](https://laravel.com/docs/master/sanctum) instead. To use Sanctum, you'll need to [store users in the database](/tips/storing-users-in-a-database) and add the `auth:sanctum` middleware to the REST API's middleware group: ```php $this->app[\Illuminate\Contracts\Http\Kernel::class]->prependMiddlewareToGroup(config(‘statamic.api.middleware’), ‘auth:sanctum’); ``` ================================================ FILE: content/collections/pages/revisions.md ================================================ --- title: Revisions intro: Revisions adds an entire publishing workflow to your authoring process. You can create revisions, review and rollback to previous revisions of your content, and more. template: page blueprint: page id: 6177b316-0eed-4dec-83d1-e5a48a8e00b6 pro: true --- ## Overview Revisions is Statamic's publishing workflow feature which provides different _states_ and corresponding behaviors for your entries — published, unpublished, working copy, and revision.
    Revisions Revisions
    Leave notes describing your updates. Kinda like Git!
    ## Enabling Revisions is a **Pro feature**, make sure you've [enabled Pro](/licensing). Enable revisions globally by setting `STATAMIC_REVISIONS_ENABLED=true` in your `.env` file. Now you can set `revisions: true` on any or all collections you'd like to use revisions. :::best-practice We recommend leaving Revisions **off** while your site is in development. It'll add extra steps to each update to your content, slow you down, and you'll probably end up annoyed by what's actually a really awesome feature. ::: ## Storage Revisions are tucked away in the `storage/statamic/revisions` directory by default. Out of the box, this directory is ignored by Git. If you want to version control your revisions, you could tweak the `.gitignore` file, however a better solution would be to move the directory to somewhere more visible: ``` php // config/statamic/revisions.php 'path' => base_path('content/revisions'), ``` ## Revision states ### Unpublished A new entry begins in the unpublished state. As long as your entry _remains_ unpublished, you're simply working directly on the entry located in your content/collections/{collection} directory. It will not be visible from the front-end of your site until it's published, and you can save a revision at any point. ### Revision Revisions are [stored as YAML files](#storage) and include all the data for your entries at the time of revisions, including additional meta data about the author, timestamp, and so on. Revisions can be previewed and restored as the [working copy](#working-copy) so you can edit and/or publish them if you wish. ### Working Copy The working copy, if you have one, is stored along with your revisions. At no point do you ever directly edit and save changes to the published (aka "live") entry. ### Published Publishing an entry will create a revision, at which point any additional changes to your entry will be stored on the _working copy_ until you choose to publish them. This will let you collaborate and improve existing content without pushing changes live or dealing with feature branches in git (something beyond most content writers and editors). ### Unpublishing Unpublishing an entry will create a revision and remove it from the front-end, at which point you begin working directly on the entry again. ## History The history view will show you all revisions, publish, unpublish, and restore states, and let you preview and restore from any previous point of the entry.
    Revisions Revisions
    This is your revision history. You can tell because it says so.
    ## Workflow For those interested in the super-granular details, here is the result of each possible state change: ### Saving an *unpublished* entry - No revision is created. - The actual entry is saved. - The actual entry is considered the working copy. ### Saving a *published* entry - The working copy is saved. - The actual entry is _not_ saved. ### Publishing an entry - The entry gets updated with the contents of the working copy, marked as published, and saved. - A revision is created. - The working copy is deleted. ### Unpublishing an entry - The entry is marked as unpublished, and saved. - A revision is created. - The working copy is deleted. ### Manually creating a revision - A revision is created. ### Restoring a revision while the entry is *published* - The working copy is updated to the contents of the revision. - The actual entry is left untouched. ### Restoring a revision while the entry is *unpublished* - The actual entry is updated to the contents of the revision. - The entry is left unpublished, even if the selected revision was published. ================================================ FILE: content/collections/pages/routing.1.md ================================================ --- id: 421a9f22-bc1c-45e9-81ca-2ba8ad2a5744 blueprint: page title: Routing intro: 'You can register Control Panel routes to build custom pages.' --- :::tip This guide is intended for apps adding routes to the Control Panel. If you're building an addon, please see our [Building an Addon](/addons/building-an-addon#routing) guide instead. ::: To register a custom route: 1. Create a routes file. Name it whatever you want, for example: `routes/cp.php` 2. Then push the routes by adding this to your `app/Providers/AppServiceProvider.php`: ```php use Illuminate\Support\Facades\Route; use Statamic\Statamic; public function boot() { Statamic::pushCpRoutes(function () { Route::namespace('\App\Http\Controllers')->group(function () { require base_path('routes/cp.php'); }); }); } ``` 3. Any routes in the file will have the appropriate name prefix and middleware applied. ================================================ FILE: content/collections/pages/routing.md ================================================ --- id: 8d9cfb16-36bf-45d0-babb-e501a35ddae6 blueprint: page title: Routing template: page intro: 'Statamic has several ways it routes requests and defines URLs and patterns, all of which are listed and described in this section.' --- ## Overview All site requests are handled by Statamic unless you [create your own Laravel routes](#laravel-routes). Here are the ways Statamic defines URLs. ## Content routes [Collection entries](/collections#routing) and [taxonomy terms](/taxonomies#routing) can have their own URLs as defined by their own flexible route patterns in their respective configuration areas. ## Statamic routes Statamic provides a `Route::statamic()` method to do all the CMS "magic" for you, like injecting data (globals and system variables, for example), applying middleware, fetching the view, layout, and so on. ``` php Route::statamic('uri', 'view', ['foo' => 'bar']); ``` ::tabs ::tab antlers ```antlers {{ myglobal }} // globals are available {{ foo }} // bar ``` ::tab blade ```blade {{ $myglobal }} // globals are available {{ $foo }} // bar ``` :: The first argument is the URI, the second is the name of the [template](/views#templates), and the third is an optional array of additional data. When the template is the same as the URI, you can provide the one argument and Statamic will fall back to use the URI as the template: ```php Route::statamic('my-page'); // Implies 'my-page' Route::statamic('/my-page'); // Implies 'my-page' Route::statamic('/foo/bar'); // Implies 'foo.bar' ``` ### Parameters You may use wildcard parameters in your routes. This allows you to match multiple URLs with the same route. ``` php Route::statamic('things/{thing}', 'things.show'); ``` The parameter values will be available in your templates. For example, if you visited `/things/foo`: ``` {{ thing }} ``` ```html foo ``` ### Layout When using `Route::statamic()`, Statamic will automatically inject the selected view into the default layout. You can customize which layout is used by adding a `layout` to the route data. ``` php Route::statamic('uri', 'view', ['layout' => 'custom']); ``` ### Content type headers You can control the content type headers by setting `'content_type' => '{content_type}'`. To make your life easier we also support a few shorthand syntaxes for the most common content types. Nobody wants to memorize this stuff, ourselves included. | Shorthand | Resolves to | |-----------|-------------| | `json` | `application/json` | | `xml` | `text/xml` | | `atom` | `application/atom+xml` (ensures `utf8` charset) | ### Dynamic closure based routes If needed, you can define more dynamic view or data logic by passing a closure. For example, you might want to dynamically return a view based on dynamic segments in your URI. You can do this by passing a closure into the second argument: ```php Route::statamic('/{component}/{mode}', function ($component, $mode) { return view($component, ['mode' => $mode]); }); ``` By returning `view()` from a closure, Statamic will still apply [all the "magic"](#statamic-routes) like middleware, layout, globals, system variables, etc. _Note: If you don't return `view()`, middleware will still get applied, but layout, globals, system variables, etc. will not be. For example, returning an array would output JSON, just like it would with `Route::get()` in Laravel, but with Statamic's middlware stack applied._ #### Dynamic route data Or, maybe you just want to dynamically compose data that's passed into a static view. You can do this by passing a closure into the third data argument: ```php Route::statamic('stats/{category}', 'statistics.show', function ($category) { return ['stats' => Stats::gatherDataExpensively($category)]; }); ``` _Note: Passing closures into both the second and the third parameter are not supported. If you need to dynamically handle both your view and your data, pass a closure into the second argmuent [as detailed above](#dynamic-closure-based-routes)._ #### Dependency injection You may also type-hint dependencies in your closure based routes, just as you can [with Laravel](https://laravel.com/docs/routing#dependency-injection): ```php use Illuminate\Http\Request; Route::statamic('stats', 'statistics.show', function (Request $request) { return ['stats' => Stats::gatherDataExpensively($request->category)]; }); ``` ### Disabling {#disabling-statamic-routes} If you want to defer **everything** to explicit Laravel routes (perhaps you're using Statamic as a headless CMS or API), you can disable this behavior by setting it in `config/statamic/routes.php`. ``` php // config/statamic/routes.php 'enabled' => false, ``` ## Laravel routes You can also configure regular Laravel routes much like you would in a regular Laravel application in `routes/web.php`. You can use closures, point to a [controller](/controllers), and so on. This is [standard Laravel stuff](https://laravel.com/docs/routing) and the standard Laravel docs apply. :::tip If you're using [Static Caching](/static-caching), make sure to add Statamic's `Cache` middleware to any Laravel routes so they get static-ly cached. ```php Route::get('/thingy', function () { // ... })->middleware(\Statamic\StaticCaching\Middleware\Cache::class); ``` ::: ## Redirects Creating redirects can be done in your `routes/web.php` using native Laravel Route methods: ``` php Route::redirect('/here', '/there'); Route::redirect('/here', '/there', 301); Route::permanentRedirect('/here', '/there'); ``` [More details on the Laravel docs](https://laravel.com/docs/routing#redirect-routes). ## Absolute domain redirects Domain names ending in a dot (e.g. `https://example.com./`) are technically valid "absolute" domains per [RFC 1034](https://www.rfc-editor.org/rfc/rfc1034) and [RFC 1035](https://www.rfc-editor.org/rfc/rfc1035), but they can cause real problems: - Browsers treat them as a different origin for CORS, so scripts, fonts, and other cross-origin assets may fail to load. - If a user first hits your site via the dot variant while [static caching](/static-caching) is active, the cached HTML will contain dot-suffixed internal links and poison the cache for every subsequent visitor. Statamic ships with a `RedirectAbsoluteDomains` middleware that redirects `https://example.com./foo` → `https://example.com/foo`. It's opt-in — register it application-wide in `bootstrap/app.php`: ```php ->withMiddleware(function (Middleware $middleware) { $middleware->append(\Statamic\Http\Middleware\RedirectAbsoluteDomains::class); // [tl! add] }) ``` :::tip Append it at the app level (not just the `web` group) so it also covers the control panel and any other routes. ::: ## Error pages Whenever an error is encountered, a view will be rendered based on the status code. It will look for the view in `resources/views/errors/{status_code}.antlers.html`. You can use a custom layout for errors by creating a `resources/views/errors/layout.antlers.html` view. Statamic will automatically render `404` pages for any unhandled routes. :::tip For 5xx errors (e.g. 500, 503, etc) only the template will be rendered. It will not be injected into a layout. ::: ================================================ FILE: content/collections/pages/scheduling.md ================================================ --- id: 0df63f01-4b97-4c15-89a9-015c02ea3748 blueprint: page title: Task Scheduling intro: "Manage scheduled tasks using Laravel's task scheduler." template: page related_entries: - 7202c698-942a-4dc0-b006-b982784efb03 - ffa24da8-3fee-4fc9-a81b-fcae8917bd74 --- Statamic leverages task scheduling via Laravel's Task Scheduler. In a nutshell, you can create a single cron job which will allow things to happen on a schedule, without any visitors needing to be on the site. [Learn more about scheduling tasks in the Laravel docs](https://laravel.com/docs/13.x/scheduling) ## Running the scheduler ### In production In production, you will need to set up a single once-per-minute cron entry that runs the `schedule:run` Artisan command. Using a service like Laravel Forge makes this simple. ```sh * * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1 ``` ### In development Typically, you would not add a scheduler cron entry to your local development machine. Instead, you may use the `schedule:work` Artisan command. This command will run in the foreground and invoke the scheduler every minute until you terminate the command: ```sh php artisan schedule:work ``` [Learn more about running the scheduler](https://laravel.com/docs/13.x/scheduling#running-the-scheduler) ## Included tasks The following tasks will be executed whenever the task scheduler is running, without you needing to enable anything. ### EntryScheduleReached Statamic will dispatch a `Statamic\Events\EntryScheduleReached` event whenever a scheduled entry reaches its target date. This event is used in multiple places such as updating search indexes and invalidating caches. The event will be dispatched on the minute _after_ the scheduled time. ## Defining schedules One way to add your own scheduled tasks is by adding items to your `routes/console.php` file. ```php Schedule::command('my-command')->daily(); Schedule::job(new Heartbeat)->everyFiveMinutes(); ``` [Learn more about defining schedules](https://laravel.com/docs/13.x/scheduling#defining-schedules) ================================================ FILE: content/collections/pages/search.md ================================================ --- id: 420f083d-99be-4d54-9f81-3c09cb1f97b7 blueprint: page title: Search intro: "Help your visitors find what they're looking for with search. Use configurable indexes to configure which fields are important, which aren't, and fine-tune your way to relevant results." template: page related_entries: - 5fcf5a56-c120-4988-a4c7-0c5e942327b7 - 2022056a-d901-423a-aaa7-ee04fff40739 - fe8ec156-447d-4f03-974f-0251a8c53244 updated_by: 3a60f79d-8381-4def-a970-5df62f0f5d56 updated_at: 1633035293 --- ## Overview There are four components (coincidentally, the same number of Ninja Turtles) whose powers combine to provide you fully comprehensive powers of search. 1. Forms 2. Results 3. Indexes 4. Drivers {.c-list-turtles} ## Forms The search form is the entry point to your site search. Search forms are basic, vanilla HTML forms with a `text` or `search` input named `q` submitting to any URL with a `search:results` tag in its view template. You can create that page however you wish: it could be an entry, a custom route, or something even fancier we didn't think of. This [Laracasts video](https://laracasts.com/series/learn-statamic-with-jack/episodes/11) shows how to setup search quickly. ``` ``` ## Results Results are powered by the [Search Results](/tags/search) tag. The tag will look for that `q` input sent by the [form](#forms) as a query string in the URL (e.g. `/search/results?q=where's%20the%20beef`). Inside the tag you have access to all the content and variables from each result. ::tabs ::tab antlers ```antlers {{ search:results index="default" }} {{ if no_results }}

    No results found for {{ get:q }}.

    {{ else }}

    {{ title }}

    {{ description | truncate:180 }}

    {{ /if }} {{ /search:results }} ``` ::tab blade ```blade @forelse ($results as $result)

    {{ $result->title }}

    {{ Statamic::modify($result->description)->truncate(180) }}

    @empty

    No results found for {{ get('q') }}.

    @endforelse
    ``` :: The tag has a lot more fine-tuned control available, like renaming the query parameter, filtering on fields and collections, and so on. You can read more about it in the [search results tag](/tags/search) docs. ## Indexes A search index is an ephemeral copy of your content, optimized for speed and performance while executing search queries. Without indexes, each search would require a scan of every entry in your site. Not an efficient way to go about it, but still technically possible. Indexes are configured in `config/statamic/search.php` and you can create as many as you want. Each index can hold different pieces of content — and any one piece of content may be stored in any number of indexes. :::tip An **index** is a collection of **records**, each representing a single search item. A record might be an entry, a taxonomy term, or even a user. ::: Your site's default index includes _only_ the title from _all_ collections. The default config looks like this: ``` php 'default' => [ 'driver' => 'local', 'searchables' => 'content', 'fields' => ['title'], ], ``` ### Search a specific index The index you wish you to search can be specified as a parameter on your [search results](#results) tag. ::tabs ::tab antlers ```antlers {{ search:results index="docs" }} ... {{ /search:results }} ``` ::tab blade ```blade ... ``` :: ### Searchables The searchables value determines what items are contained in a given index. By passing an array of searchable values you can customize your index however you'd like. For example, to index all blog posts and news articles together, you can do this: ``` php 'searchables' => ['collection:blog', 'collection:news'] ``` #### Possible options include: - `content` - `collection:*` - `collection:{collection handle}` - `taxonomy:{taxonomy handle}` - `assets:{container handle}` - `users` - Custom ones may be added by addons. [Read about creating custom searchables](/extending/search) ### Filtering searchables You may choose to allow only a subset of searchable items. For example, you may want to have a toggle field that controls whether an entry gets indexed or not. You can specify a closure with that logic. ```php 'searchables' => ['collection:blog'], 'filter' => function ($item) { return $item->status() === 'published' && ! $item->exclude_from_search; } ``` Now, only published entries that don't have the `exclude_from_search` toggle field enabled will be indexed. :::tip Heads up Your filter will override any native filters. For example, entries will filter out drafts by default. If your filter doesn't also remove drafts, they could be indexed. ::: Alternatively you can specify a class to handle the filtering. This is useful when you want to cache your config using `php artisan config:cache`. ``` php 'filter' => \App\SearchFilters\BlogFilter::class, ``` ``` php namespace App\SearchFilters; class BlogFilter { public function handle($item) { return $item->status() === 'published' && ! $item->exclude_from_search; } } ``` ### Records & fields The best practice for creating search indexes is to simplify your record structure as much as possible. Each record should contain only enough information to be discoverable on its own, and no more. You can customize this record by deciding which _fields_ are included in the index. ### Transforming fields By default, the data in the entry/term/etc that corresponds to the `fields` you've selected will be stored in the index. However, you're able to tailor the values exactly how you want using `transformers`. Each transformer is a closure that would correspond to a field in your index's `fields` array. ``` php 'fields' => ['title', 'address'], 'transformers' => [ // Return a value to store in the index. 'title' => function ($title) { return ucfirst($title); }, // Return an array of values to be stored. // These will all be separate searchable fields in the index. // $value is the current value // $searchable is the object that $value has been plucked from 'address' => function ($value, $searchable) { return [ '_geoloc' => $value['geolocation'], 'location' => $value['location'], 'region' => $value['region'], ]; } ] ``` Alternatively you can specify a class to handle the transformation. This is useful when you want to cache your config using `php artisan config:cache`. ``` php 'fields' => ['title', 'address'], 'transformers' => [ 'title' => \App\SearchTransformers\MyTransfomer::class, ] ``` ``` php namespace App\SearchTransformers; class MyTransformer { public function handle($value, $field, $searchable) { // $value is the current value // $field is the index from the transformers array // $searchable is the object that $value has been plucked from return ucfirst($value); } } ``` ### Updating indexes Whenever you save an item in the Control Panel it will automatically update any appropriate indexes. If you edit content by hand, you can tell Statamic to scan for new and updated records via the command line. ``` shell # Select which indexes to update php please search:update # Update a specific index php please search:update name # Update all indexes php please search:update --all ``` ### Connecting indexes When a search is performed in the control panel (in collections, taxonomies, or asset containers, for example), Statamic will search the configured index for that content type. If an index _hasn't_ been defined or specified, Statamic will perform a less efficient, generic search. It'll be slower and less relevant, but better than nothing. You can define which search index will be used by adding it to the respective YAML config file: ``` yaml # content/collections/blog.yaml title: Blog search_index: blog ``` :::tip About entries After specifying that an index contains entries from a collection (in [searchables](#searchables)), you **must also** specify the index in the collection config itself because collections and entries can be in multiple indexes. Also, since draft entries are not included in search indexes by default, you'll want to include them for your collection-linked index. You can add a filter that allows everything. ```php 'articles' => [ 'driver' => 'local', 'searchables' => ['collection:articles'], 'filter' => fn () => true, // [tl! ++] ] ``` ::: ### Localization You may choose to use separate indexes to store localized content. For example, English entries go in one index, French entries go in another, and so on. Take these site and search configs for example: ```yaml # resources/sites.yaml en: url: / fr: url: /fr/ de: url: /de/ ``` ```php // config/statamic/search.php 'indexes' => [ 'default' => [ 'driver' => 'local', 'searchables' => 'content', ] ] ``` By default, all entries will go into the `default` index, regardless of what site they're in. You can enable localization by setting the `sites` you want. ```php 'indexes' => [ 'default' => [ 'driver' => 'local', 'searchables' => 'content', 'sites' => ['en', 'fr'], // You can also use "all" [tl! ++ **] ] ] ``` This will create dynamic indexes named after the specified sites: - `default_en` - `default_fr` If you have a localized index and include searchables that do not support localization (like assets or users), they will appear in each localized index. ### Customizing index names You can override how index names are generated by registering a callback in a service provider's `boot` method. This is handy when you want to prefix names by environment to avoid collisions between staging and production when using a shared cloud provider like Algolia. ```php use Statamic\Search\Algolia\Index as AlgoliaIndex; public function boot() { AlgoliaIndex::resolveNameUsing(fn ($name, $locale) => app()->environment().'_'.($locale ? $name.'_'.$locale : $name)); } ``` The callback receives the configured `$name` and the `$locale` (or `null` for non-localized indexes). Statamic's default naming logic is bypassed entirely, so if you want the locale suffix, append it yourself. ## Drivers Statamic takes a "driver" based approach to search engines. Drivers are interchangeable so you can gain new features or integrate with 3rd party services without ever having to change your data or frontend. The native [local driver](#local-driver) is simple and requires no additional configuration, while the included [algolia driver](#algolia-driver) makes it super simple to integrate with [Algolia](https://algolia.com), one of the leading search as a service providers. You can build your own custom search drivers or look at the [Addon Marketplace](https://statamic.com/addons/tags/search) to see what the community has already created. ### Local {#local-driver} The `local` driver (aka "Comb") uses JSON to store key/value pairs, mapping fields to the content IDs they belong to. It lacks advanced features you would see in a service like Algolia, but hey, It Just Works™. It's a great way to get a search started quickly. #### Settings You may provide local driver specific settings in a `settings` array. ```php 'driver' => 'local', 'searchables' => 'content', // [tl! **:start] 'min_characters' => 3, 'use_stemming' => true, // [tl! **:end] ``` - `match_weights`: An array of weights for each field to use when calculating relevance scores. Defaults to: ```php [ 'partial_word' => 1, 'partial_first_word' => 2, 'partial_word_start' => 1, 'partial_first_word_start' => 2, 'whole_word' => 5, 'whole_first_word' => 5, 'partial_whole' => 2, 'partial_whole_start' => 2, 'whole' => 10, ] ``` - `min_characters`: The minimum number of characters required in a search query. Defaults to `1`. - `min_word_characters`: The minimum number of characters required in a word in a search query. Defaults to `2`. - `score_threshold`: The minimum score required for a result to be included in the search results. Defaults to `1`. - `property_weights`: An array of weights for each property to use when calculating relevance scores. Defaults to `[]`. - `query_mode`: The query mode to use when searching (e.g. "whole", "words", "boolean"). Defaults to `boolean`. - `use_stemming`: Whether to match singular and plural forms of each word (e.g. "cats" matches "cat"). Powered by Laravel's `Str::singular()`, so it handles English inflection only — not true verb stemming like "jumping" → "jump". Only applies when `query_mode` is `words` or `boolean`. Defaults to `false`. - `use_alternates`: Whether to match a small set of typographic alternates in the query. Specifically: `and` ↔ `&`, and swapping between straight (`'`) and curly (`‘` / `’`) apostrophes. It does _not_ handle US/UK spelling differences (e.g. "color" / "colour") or synonyms. Only applies when `query_mode` is `words` or `boolean`. Defaults to `false`. - `include_full_query`: Whether to include the full search query in the search results. Defaults to `true`. - `stop_words`: An array of stop words to exclude from the search query. Defaults to `[]`. - `limit`: Whether to limit the number of results returned. Defaults to `null`. - `enable_too_many_results`: Whether to enable a warning when too many results are returned. Defaults to `false`. - `sort_by_score`: Whether to sort the search results by relevance score. Defaults to `true`. - `group_by_category`: Whether to group the search results by category. Defaults to `false`. - `exclude_properties`: An array of properties to exclude from the search results. Defaults to `[]`. - `include_properties`: An array of properties to include in the search results. Defaults to `[]`. ### Algolia {#algolia-driver} Algolia is a full-featured search and navigation cloud service. They offer fast and relevant search with results in under 100 ms (99% under 20 ms). Results are prioritized and displayed using a customizable ranking formula. ``` php 'default' => [ 'driver' => 'algolia', 'searchables' => 'content', ], ``` To set up the Algolia driver, create an account on [their site](https://www.algolia.com/), drop your API credentials into your `.env`, and install the composer dependency. ``` env ALGOLIA_APP_ID=your-algolia-app-id ALGOLIA_SECRET=your-algolia-admin-key ``` ``` shell composer require algolia/algoliasearch-client-php:^3.4 ``` Statamic will automatically create and sync your indexes as you create and modify entries once you kick off the initial index creation by running the command `php please search:update`. #### Settings You may provide Algolia-specific [settings](https://www.algolia.com/doc/api-reference/settings-api-parameters/) in a `settings` array. ```php 'driver' => 'algolia', 'searchables' => 'content', 'settings' => [ // [tl! **:start] 'attributesForFaceting' => [ 'filterOnly(post_tags)', 'filterOnly(post_categories)', ], 'customRanking' => [ 'asc(attribute1)', 'desc(attribute2)', 'typo', 'words' ] ] // [tl! **:end] ``` #### Templating with Algolia We recommend using the [Javascript implementation](https://www.algolia.com/doc/api-client/getting-started/install/javascript/?language=javascript) to communicate directly with them for the frontend of your site. This bypasses Statamic entirely in the request lifecycle and is incredibly fast. ## Extras ### Config cascade You can add values into the defaults array, which will cascade down to all the indexes, regardless of which driver they use. You can also add values to the drivers array, which will cascade down to any indexes using that respective driver. A good use case for this is to share API credentials across indexes. Any values you add to an individual index will only be applied there. ## Control Panel Statamic configures a `cp` search index behind the scenes, used by the Command Palette. By default, it uses the `local` driver and includes content (entries/terms/assets), users, and any addon-provided searchables. You can override the configuration in your `search.php` config file: ```php // config/statamic/search.php 'indexes' => [ // ... 'cp' => [ 'driver' => 'local', 'searchables' => ['content', 'users', 'addons'], 'fields' => ['title'], ], ], ``` ## Digging deeper Search is split into a handful of different parts behind the scenes. - An `Index` class will exist for each configured index. It will know how to send data to and from the underlying driver. - `Searchable` classes are the things that can be indexed and searched. (e.g. an `Entry`) - `ProvidesSearchables` classes (or just "Providers") are classes that define all the applicable searchable items. (e.g. an `Entries` provider gives `Entry` instances.) - `Result` classes are wrappers that allow Statamic to use the searchable objects in a consistent way. (e.g. each result should have an identifier, type, Control Panel URL, etc) ### Custom Searchables In order to allow searching of custom items, you must create a provider and make your object implement Searchable. In the following examples, we'll assume you are wanting to store products as Eloquent models. #### Implement Searchable The object you want to make searchable should implement both the `Statamic\Contracts\Data\Augmentable` and `Statamic\Contracts\Search\Searchable` interfaces. To make things easier, you can import the `HasAugmentedData` and `Searchable` traits which will handle most of the heavy lifting for you. ```php use Illuminate\Database\Eloquent\Model; use Statamic\Contracts\Data\Augmentable; use Statamic\Contracts\Search\Result as ResultContract; use Statamic\Contracts\Search\Searchable as SearchableContract; use Statamic\Data\HasAugmentedData; use Statamic\Search\Result; use Statamic\Search\Searchable; class Product extends Model implements Augmentable, ContainsQueryableValues, SearchableContract { use HasAugmentedData, Searchable; /** * The identifier that will be used in search indexes. */ public function getSearchReference(): string { return 'product::'.$this->id; } /** * The indexed value for a given field. */ public function getSearchValue(string $field) { return $this->$field; } /** * Convert to a search result class. * You can use the Result class, or feel free to create your own. */ public function toSearchResult(): ResultContract { return new Result($this, 'product'); } /** * Returns an array of the model's attributes to be used in augmentation. */ public function augmentedArrayData() { return $this->attributesToArray(); } } ``` When you're making a searchable from an Eloquent model, you'll need to override some `offset`/`__call` methods to prevent conflicts between Eloquent and Statamic's implementations of those methods: ```php /** * These methods exist on both Eloquent's Model class and in Statamic's HasAugmentedInstance trait. * To prevent conflicts, we need to override these methods and force them to call Eloquent's method. */ public function offsetGet($offset): mixed { return parent::offsetGet($offset); } public function offsetSet($offset, $value): void { parent::offsetSet($offset, $value); } public function offsetUnset($offset): void { parent::offsetUnset($offset); } public function offsetExists($offset): bool { return parent::offsetExists($offset); } public function __call($method, $parameters) { return parent::__call($method, $parameters); } ``` #### Create Provider You should have a class that implements `ProvidesSearchables`, however it's even easier for you to extend `Provider`. ```php use Statamic\Search\Searchables\Provider; class ProductProvider extends Provider { /** * The handle used within search configs. * * e.g. For 'searchables' => ['collection:blog', 'products:hats', 'users'] * they'd be 'collection', 'products', and 'users'. */ protected static $handle = 'products'; /** * The prefix used in each Searchable's reference. * * e.g. For 'entry::123', it would be 'entry'. */ protected static $referencePrefix = 'product'; /** * Convert an array of keys to the actual objects. * The keys will be searchable references with the prefix removed. */ public function find(array $keys): Collection { return Product::find($keys); } /** * Get all searchables and return a collection of * their references. * * e.g. 'entry::123' */ public function provide(): Collection { return Product::query() ->pluck('id') ->map(fn ($id) => "product::{$id}"); // If you wanted to allow subsets of products, you could specify them in your // config then retrieve them appropriately here using keys. // e.g. 'searchables' => ['products:hats', 'products:shoes'], // $this->keys would be ['keys', 'hats']. return Product::whereIn('type', $this->keys) ->pluck('id') ->map(fn ($id) => "product::{$id}"); } /** * Check if a given object belongs to this provider. */ public function contains($searchable): bool { return $searchable instanceof Product; } } ``` #### Register the Provider In order to use the provider, first register it within a service provider, and then it will be available to your search config by its handle. ```php use Statamic\Facades\Search; public function boot() { ProductProvider::register(); } ``` ```php // config/statamic/search.php 'indexes' => [ 'products_and_blog_posts' => [ 'driver' => 'local', 'searchables' => [ 'products', // [tl! ++] 'collections:blog' ], 'fields' => ['title'] ] ] ``` You can also include your searchable in the `content` or `addons` wildcard searchables, which are used by default for front-end and Control Panel searches. ```php // Pass the handle... Search::addContentSearchable('product'); Search::addCpSearchable('order'); // Or the provider class... Search::addContentSearchable(ProductsProvider::class); Search::addCpSearchable(OrdersProvider::class); ``` #### Event Listeners You will want to update the indexes when you create, edit, or delete your searchable items. Continuing with the Eloquent example, we can hook into model events in your service provider. ```php use Illuminate\Support\Facades\Event; use Statamic\Facades\Search; Event::listen('eloquent.saved: App\Models\Product', function ($model) { Search::updateWithinIndexes($model); }); Event::listen('eloquent.deleted: App\Models\Product', function ($model) { Search::deleteFromIndexes($model); }); ``` The `updateWithinIndexes` method will update the record in all appropriate indexes. If a filter determines that the record should be removed (e.g. if it changed into a draft), it'll remove it. The `deleteFromIndexes` method will remove it from all appropriate indexes. ### Custom Index Drivers Statamic comes with two native search index drivers: Comb and Algolia. Comb is our "local" driver, where indexes are stored as json files. Algolia integrates with the service using their API. For this example, we'll integrate with a fictional service called FastSearch. #### Create Index You should have a class that extends `Index`. ```php use Statamic\Search\Index; class FastSearchIndex extends Index { private $client; public function __construct(FastSearchClient $client, $name, array $config, string $locale = null) { // In this example, we'll accept a fictional client class that will perform API requests. // If you have a constructor, don't forget to construct the parent class too. $this->client = $client; parent::__construct($name, $config, $locale); } /** * Return a query builder that will perform the search. */ public function search($query) { return (new FastSearchQuery($this))->query($query); } /** * Check whether the index actually exists. * i.e. Does it exist in the service, or as a json file, etc. */ public function exists() { $this->client->indexExists($this->name); } /** * Insert items into the index. */ public function insertDocuments(Documents $documents) { $this->client->insertObjects($documents->all()); } /** * Delete an item from the index. */ public function delete($document) { $this->client->deleteObject($document); } /** * Delete the entire index. */ protected function deleteIndex() { $this->client->deleteIndex($this->name); } } ``` #### Register Index ```php public function boot() { Search::extend('fast', function ($app, $config, $name) { $client = new FastSearchApiClient('api-key'); return new FastSearchIndex($client, $name, $config); }); } ``` #### Create Query Builder In the index class, the `search` method wanted a query builder. You can create a class that extends our own, which only requires you to define a single method. ```php 'One', 'search_score' => 500], * ['title' => 'Two', 'search_score' => 400], * ] */ public function getSearchResults() { $results = $this->index->searchUsingApi($query); // Statamic will expects a search_score to be in each result. // Some services like Algolia don't have scores and will just return them in order. // This is a trick to set the scores in sequential order, highest first. return $results->map(function ($result, $i) use ($results) { $result['search_score'] = $results->count() - $i; return $result; }); } } ``` This `getSearchResults` method is used in the parent class in order to allow basic filtering and other query methods. Of course, you are free to build as much of your own query builder as you like. ================================================ FILE: content/collections/pages/sites-api.md ================================================ --- id: 124cb9e8-b5e0-4af6-8271-98ca6b0131b4 blueprint: page title: 'Sites API' intro: 'We have an API that can be used to manage your Statamic Sites in your [statamic.com](https://statamic.com) account. This is most useful with our Platform Plan, which you can [contact us](https://statamic.com/support) directly about for more information.' --- ## Authentication To access the sites API, you'll need to create a token in your statamic.com account. All of the following endpoints require that your request contains an `Authorization` header with your token preceded by `Bearer`. ### Example Headers | Header Name | Header Value | | --- | --- | | `Authorization` | `Bearer 44\|seLDYsDrqyxS2cT8PYremnysuqrovpYHSJ1lzjb3` | | `Accept` | `application/json` | ### Using Laravel's HTTP Facade If you are using Laravel's `Http` facade to make your requests, you can use the `acceptJson()` and `withToken()` helper methods to make simple JSON requests with your bearer token: ```php Http::acceptJson() ->withToken($token) ->post('https://statamic.com/api/v1/sites', $payload); ``` _*For more info, read more about [headers](https://laravel.com/docs/13.x/http-client#headers) and [bearer tokens](https://laravel.com/docs/13.x/http-client#bearer-tokens) in Laravel.*_ ## Endpoints - [Sites Index](#sites-index) - [Create Site](#create-site) - [Delete Site](#delete-site) ### Sites Index #### `GET https://statamic.com/api/v1/sites` #### Example Output ```json { "data": [ { "name": "Wayne's World", "key": "pg4x2qrly2my8dl1", "domains": [ "waynesworld.ca" ], "created_at": "2021-11-19 09:32:52" }, { "name": "Bobby's World", "key": "1o0xe7rzdd9wq58j", "domains": [ "bobbysworld.ca" ], "created_at": "2021-11-19 09:33:10" } ] } ``` ### Create Site #### `POST https://statamic.com/api/v1/sites` #### Params | Param | Required | Example | | --- | --- | --- | | `name` | yes | `Jurassic World` | | `domain` | no | `jurassicworld.ca` | #### Example Output ```json { "data": { "name": "Jurassic World", "key": "pwkknrxl6y7z1n9v", "domains": [ "jurassicworld.ca" ], "created_at": "2021-11-18 19:45:41" } } ``` ### Delete Site #### `DELETE https://statamic.com/api/v1/sites/[your-site-key-here]` #### Example Output ```json { "message": "Site [pwkknrxl6y7z1n9v] deleted." } ``` ================================================ FILE: content/collections/pages/slugs.md ================================================ --- title: Slugs id: d84613d5-2b2b-465d-8f02-c71b6d2aadf1 --- ## Slugify Vue Component You can use the `` component to generate a slug based off another property: ``` html ``` When the value of the `from` prop changes (ie. the `title` property), it will update the `slug` property. If you update the slug manually (ie. by typing in the field), the component will realize, and prevent any further automatic slug generation. If you want underscores instead of dashes, you can pass in `separator="_"`. ## JavaScript API ### Basic Slugs You may also create slugs programmatically. ```js import { slug } from '@statamic/cms/api'; slug.create('Hello World'); // hello-world ``` You may also define the separating character: ```js import { slug } from '@statamic/cms/api'; slug.separatedBy('_').create('Hello World'); // hello_world ``` You may use the `str_slug` and `snake_case` global methods respectively as aliases for both of these: ```js str_slug('Hello World'); // hello-world snake_case('Hello World'); // hello_world ``` ### More Oomph When you need more accurate slugs, you can leverage PHP's more powerful slug logic. By calling `async`, the `create` method will become Promise-based as it requests slugs from the server: ```js import { slug } from '@statamic/cms/api'; slug.async().create('Hello World').then(slug => { console.log(slug); // 'hello-world' }) ``` This is particularly useful when you need to provide the language: ```js import { slug } from '@statamic/cms/api'; slug.in('zh').async().separatedBy('_') .create('你好世界') .then(slug => console.log(slug)); // ni_hao_shi_jie ``` :::tip If you don't provide a language, the language of the selected site in the control panel will be used. ::: ### Debouncing If you will be calling this repeatedly, such as via user's keystrokes, debouncing is useful to prevent excess calls to the server. Debouncing will be automatically handled as long as you call `create` on the same instance: ```js import { slug } from '@statamic/cms/api'; const slugger = slug.async().separatedBy('_'); slugger.create('one').then(slug => console.log(slug)); slugger.create('two').then(slug => console.log(slug)); slugger.create('three').then(slug => console.log(slug)); // console: 'three' ``` ================================================ FILE: content/collections/pages/stache.md ================================================ --- title: Stache template: page intro: > Instead of using a relational database like MySQL as a storage system, Statamic aggregates the data in your content files into an efficient, index-based system and stores it in Laravel's application cache. We call this the "stache", and we like to make mustache jokes about it. blueprint: page id: 499d808b-18be-42e9-acd0-91bcdff73193 --- ## Overview **The stache is ephemeral** and can be blown away and rebuilt from scratch at any time without losing data. This is most often done when content or settings change, or when updates are deployed to a production server.
    Tom Selleck as Magnum P.I.
    Behold, the stache of all staches!
    ## The Stache is watching your files {#watcher} Each page request from the frontend or Control Panel triggers a scan of the `last_modified` timestamps on all content and configuration files in your Statamic application. When Statamic sees a change, the Stache performs selective updates to any corresponding indexes. :::best-practice This is great for local development, but on a production environment **you should make sure the watcher is disabled.** If you're editing content through the control panel, or only ever pushing content through deployments, you are adding extra overhead to every request for no reason. By setting it to `auto`, it will be enabled when running on the `local` environment (`APP_ENV=local`) and disabled everywhere else. This is the default behavior for new sites. ``` env STATAMIC_STACHE_WATCHER=auto ``` ``` php return [ 'watcher' => env('STATAMIC_STACHE_WATCHER', 'auto'), // [tl! highlight] ... ]; ``` Of course, you may set it to `false` to explicitly disable it everywhere. ::: ## Clearing the Stache {#clearing} The [CLI](/cli) has commands to clear, warm, and refresh (clear and then immediately warm) the stache. ``` shell php please stache:clear php please stache:warm php please stache:refresh ``` :::best-practice It's a good idea to perform a `php please stache:refresh` when deploying changes to your production server so they're immediately available for the next request. ::: ## Parallel warming {#parallel-warming} For large sites, you can warm stores in parallel rather than sequentially. On content-heavy projects this can cut warm times by **6x or more** with proportional drops in peak memory usage. Parallel warming is **disabled by default**. Enable it via env vars: ``` env STATAMIC_STACHE_PARALLEL_WARMING=true STATAMIC_STACHE_CONCURRENCY_DRIVER=fork STATAMIC_STACHE_MAX_PROCESSES=0 STATAMIC_STACHE_MIN_STORES_PARALLEL=3 ``` Or in `config/statamic/stache.php`: ```php // config/statamic/stache.php 'warming' => [ 'parallel_processing' => env('STATAMIC_STACHE_PARALLEL_WARMING', false), 'max_processes' => env('STATAMIC_STACHE_MAX_PROCESSES', 0), 'min_stores_for_parallel' => env('STATAMIC_STACHE_MIN_STORES_PARALLEL', 3), 'concurrency_driver' => env('STATAMIC_STACHE_CONCURRENCY_DRIVER', 'process'), ], ``` ### Concurrency drivers Powered by [Laravel's Concurrency](https://laravel.com/docs/concurrency) facade: | Driver | Description | |--------|-------------| | `process` | Spawns separate PHP processes. Works everywhere, but has the most overhead per task. The default. | | `fork` | Uses the `pcntl` extension to fork the current process. Significantly faster but **CLI-only**. Requires `spatie/fork`. | | `sync` | Runs everything sequentially in the current process. Useful for debugging. | For deploys, `fork` is almost always the right choice. Install it with: ``` shell composer require spatie/fork ``` The `fork` driver requires the `pcntl` PHP extension. It's compiled in by default on Linux and macOS (and works on Forge, Vapor, Laravel Cloud, and Herd) but is **unavailable on Windows** and is sometimes disabled on shared hosting. Verify with: ``` shell php -m | grep pcntl ``` If `pcntl` isn't available, use the `process` driver instead. ### Tuning - `max_processes` — `0` auto-detects CPU cores. Bump it up if your CI/deploy box has plenty of headroom. - `min_stores_for_parallel` — small sites with only a couple stores won't benefit from parallelism (the orchestration overhead exceeds the win), so this skips it below the threshold. :::tip Parallel warming only applies to CLI operations like `php please stache:warm` and `stache:refresh`. Web requests can't fork, so on-demand warming during a request always runs sequentially. ::: ## Stores The Stache is comprised of different stores responsible for fetching their own data sets. For instance, if you wanted to get a `Collection` object, the `CollectionStore` would be in charge. It knows that any YAML file inside `content/collections` translates into one. The following stores exist in the Stache: - `taxonomies` - `terms` (grouped by taxonomy) - `collections` - `entries` (grouped by collection) - `collection-trees` - `navigation` - `nav-trees` - `globals` - `asset-containers` - `assets`* (grouped by container) - `users` You're able to customize all the stores inside the Stache by referencing the keys above. You can change the directories for each of them. You can also change the class if you need to customize any of its logic. ```php // config/statamic/stache.php 'stores' => [ 'entries' => [ 'class' => EntriesStore::class, 'directory' => base_path('content/collections') ] ] ``` :::tip If you only want to change the `directory`, you don't need to include the `class`. ::: \* The `assets` store cannot have its directory customized here. You configure its location through the [container](/assets#containers). ### Excluding stores {#excluding-stores} You can exclude a registered store from the `stache:warm` and `stache:clear` operations. This is useful when you've [swapped out a repository](/extending/repositories) with your own implementation (e.g. a custom database-backed driver) and don't want the Stache to waste time warming or clearing stores that aren't backed by files. ```php use Statamic\Facades\Stache; Stache::exclude('entries'); Stache::exclude('terms'); ``` Call this from a service provider's `boot` method. The store stays registered (queries still work through it), but it's skipped during warming and clearing. :::tip The [Eloquent Driver](https://github.com/statamic/eloquent-driver) handles this automatically for whichever repositories you've configured it to manage. ::: ## Indexes Each store will organize data from its items into indexes. It'll then use those to narrow down items when performing queries. For instance, you will find an index of all entry titles, which might look like this: ``` txt entry-id-1: Entry One entry-id-2: Entry Two ``` ### Default indexes All stores will have a number of predefined indexes, like id and path. Some stores will have their own predefined indexes. eg. Entry stores will also have title, slug, uri, etc. ### When does indexing happen? Indexes will only be created when needed, when a query is performed. When saving an item, its corresponding values will be updated in each of its store's indexes. eg. An entry's title will be inserted into the title index, its slug into the slug index, and so on. When deleting, it will be removed from each index. Indexes may be created in advance by running the following command: ``` shell php please stache:warm ``` ### Configuring additional indexes Take this tag, for example: ``` {{ collection:blog awesome:is="true" }} ``` Under the hood, it would be doing `->where('awesome', true)`, which would look for the `awesome` index. If it didn't exist, it would create it right there. Creating an index could take some time, depending on how much content you have. If you know you will be needing these indexes in advance, you can add them to a store's configuration in `config/statamic/stache.php`: ``` php return [ 'stores' => [ // ... 'entries' => [ 'class' => Stores\EntriesStore::class, 'directory' => base_path('content/collections'), 'indexes' => [ 'awesome', ] ], // ... ], ] ``` Or, add it to all stores: ``` php return [ 'indexes' => [ 'awesome', ] ]; ``` Any additional indexes you have will be updated [when appropriate](#when-does-indexing-happen). ## Cache driver By default the Stache places its data in the default [Laravel cache store](https://laravel.com/docs/cache#configuration), there's no special configuration necessary to change it. Whatever your default caching driver is for the rest of your app is where your Stache will be. By default it's in the filesystem, but of course you can feel free to use Redis, Memcached, etc. ``` env CACHE_STORE=redis ``` If you want to change which cache store is used by the Stache, you can change the `statamic.stache.cache_store` configuration key: ```php // config/statamic/stache.php return [ 'cache_store' => 'stache-cache', ] ``` ## Locks Statamic will create indexes and build the cache on demand where appropriate. Depending on the amount of content you have, this could be a resource-heavy operation. To prevent excess CPU and memory usage, subsequent requests will be locked while the cache is being updated. When a page is requested while the cache is being updated, it will wait until it's ready. If it's not ready after the configured timeout length (default of 30 seconds), a 503 response will be served with a `` tag that'll immediately re-request the page. ``` php return [ 'lock' => [ 'enabled' => true, 'timeout' => 30, ] ] ``` ## Diving even deeper You can dive even deeper and learn how to build your own Stache Indexes and fine-tune performance with Michael Aerni's 2021 Statameet talk. :::watch https://www.youtube.com/embed/KDO2mIRjr18 Dive deeper into the Stache! ::: ================================================ FILE: content/collections/pages/starter-kits.md ================================================ --- id: 3a2e714c-57de-4b16-a916-7e7aba22de03 title: 'Starter Kits' blueprint: link redirect: url: '@child' status: 301 --- ================================================ FILE: content/collections/pages/static-caching.md ================================================ --- title: 'Static Caching' template: page blueprint: page intro: | Nothing loads faster than static pages. Instead of rendering pages dynamically on demand, Statamic can cache static pages and pass routing to Apache or Nginx with reverse proxying. id: ffa24da8-3fee-4fc9-a81b-fcae8917bd74 --- ## Important preface Certain features — such as forms with server-side validation, page protection, or content randomization — may not work with static page caching. (You may want to check out the [nocache tag](/tags/nocache) though.) As long as you understand that, you can leverage static caching for maximum performance. Whatever is on the page the first time it's visited is what will be cached for all users. For example, if you're using page protection and a user who has access visits the page, it'll be accessible to everyone. Query parameters are ignored by default, so `/blog` and `/blog?utm_source=twitter` will serve the same cached page. You can [change this behavior](#query-parameters) if needed. Protected pages are excluded from the static cache by default. If you've written a [custom protection driver](/protecting-content#custom-drivers) whose logic doesn't vary between visitors, you can opt it back into caching by marking it [cacheable](/protecting-content#cacheable-drivers). :::tip You can **alternatively** use the [static site generator](https://github.com/statamic/ssg) to pre-generate and deploy **fully static HTML sites**. ::: ## Caching strategies Each caching strategy can be configured independently. Inside `config/statamic/static_caching.php` you will find two pre-configured strategies - one for each supported driver. ``` php return [ 'strategy' => 'half', 'strategies' => [ 'half' => [ 'driver' => 'application', ], 'full' => [ 'driver' => 'file', ] ] ]; ``` Set `strategy` to the name of the strategy you wish to use, or `null` to disable static caching completely. ## Application driver The application driver will store your cached page content within Laravel's cache. We refer to this as **half measure**. This will still run every request through a full instance of Statamic but will serve all request data from a pre-rendered cache, speeding up load times often by half or more. This is an easy, one-and-done setting. ``` php return [ 'strategy' => 'half', 'strategies' => [ 'half' => [ 'driver' => 'application', ] ] ]; ``` :::tip You may use the [nocache tag](/tags/nocache) to keep parts of your pages dynamic. ::: ### Caching 404s When using the application driver, 404 responses are statically cached automatically. This stops a heavy "page not found" view from being re-rendered on every request when bots or broken links repeatedly hit non-existent URLs. You can go a step further and have Statamic share a single cached 404 across every 404-ing URL. The first 404 is rendered and cached, and every subsequent 404 — regardless of URL — is served that same cached response. Each URL still gets its own cache entry, but the rendering work is skipped. ```php // config/statamic/static_caching.php 'share_errors' => true, ``` Both behaviors are only available when using half measure. With full measure, the 404 never reaches PHP if the rewrite rules send the request to `index.php`, so there's nothing to cache. ## File driver The file driver will generate completely static `.html` pages ready for your web server to serve directly. This means that the HTML files will be loaded before it even reaches PHP. We refer to this as full measure. This is probably the lightning you seek. ⚡️ ``` php return [ 'strategy' => 'full', 'strategies' => [ 'full' => [ 'driver' => 'file', 'path' => public_path('static'), ] ] ]; ``` :::tip Heads up! When using full-measure caching, the [nocache tag](/tags/nocache) will rely on JavaScript. ::: ### Permissions Using the file driver, you can configure the permissions for the directories and files that are getting created using the `static_caching.strategies.full` config option. ```php 'strategies' => [ 'full' => [ 'driver' => 'file', 'path' => public_path('static'), 'permissions' => [ // [tl! focus] 'directory' => 0755, // [tl! focus] 'file' => 0644, // [tl! focus] ], // [tl! focus] ], ] ``` ## Server rewrite rules You will need to configure its rewrite rules when using full measure caching. Here are the rules for each type of server. :::tip If you're using Laravel Herd or Laravel Valet, you don't need to worry about configuring rewrite rules locally. They will automatically handle the rewrite rules for you. ::: ### Apache On Apache servers, you can define rewrite rules inside an `.htaccess` file: ``` htaccess RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{QUERY_STRING} !live-preview RewriteRule ^ index.php [L] RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{QUERY_STRING} live-preview RewriteRule ^ index.php [L] RewriteCond %{DOCUMENT_ROOT}/static/%{REQUEST_URI}_%{QUERY_STRING}\.html -s RewriteCond %{REQUEST_METHOD} GET RewriteRule .* static/%{REQUEST_URI}_%{QUERY_STRING}\.html [L,T=text/html] ``` :::tip When you have the `ignore_query_strings` option enabled, replace the last chunk of the `.htaccess` snippet with this: ``` htaccess RewriteCond %{DOCUMENT_ROOT}/static%{REQUEST_URI}\.html -f RewriteRule ^ static%{REQUEST_URI}\.html [L] RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^ index.php [L] ``` ::: ### Nginx :::tip If you're using [Laravel Forge](https://forge.laravel.com) and selected the "Statamic" type when creating your site, this will already be configured for you. ::: On Nginx servers, you will need to edit your `.conf` files. They are not located within your project, and may be in a slightly different place depending on your server setup. If you're using a service like [Laravel Forge](https://forge.laravel.com) or [Ploi](https://ploi.io/statamic), you can edit your `nginx.conf` from within the UI. ``` nginx set $try_location @static; if ($request_method != GET) { set $try_location @not_static; } if ($args ~* "live-preview=(.*)") { set $try_location @not_static; } location / { try_files $uri $try_location; } location @static { try_files /static${uri}_$args.html $uri $uri/ /index.php?$args; } location @not_static { try_files $uri /index.php?$args; } ``` :::tip When you have the `ignore_query_strings` option enabled, you should update the `try_files` line inside the `@static` block: ``` nginx location @static { try_files /static${uri}_$args.html $uri $uri/ /index.php?$args; # [tl! remove] try_files /static${uri}_.html $uri $uri/ /index.php?$args; # [tl! add] } ``` ::: :::tip If your site needs to support URLs with a trailing slash, make sure to update the NGINX config: ``` nginx location / { try_files $uri $try_location; # [tl! remove] try_files $uri $uri/ $try_location; # [tl! add] } location @static { try_files /static${uri}_$args.html $uri $uri/ /index.php?$args; # [tl! remove] rewrite ^/(.*)/$ /$1 last; # [tl! add] try_files /static${uri}_$args.html /static${uri}/_$args.html $uri $uri/ /index.php?$args; # [tl! add] } location @not_static { try_files $uri /index.php?$args; # [tl! remove] try_files $uri $uri/ /index.php?$args; # [tl! add] } ``` ::: ### IIS On Windows IIS servers, your rewrite rules can be placed in a `web.config` file. ``` xml ``` ## Warming the static cache Before users visit your website, you may wish to warm the static cache to make first time loads much faster. To do this, run: ``` php please static:warm ``` The `static:warm` command supports various arguments: * **`--queue`** Indicates that URIs should be warmed on the queue (in the background). * **`--insecure`** Allows the command to skip SSL verification. This can come in handy when running the site behind a reverse proxy or when using self-signed certificates, for example. * **`--user` and `--password`** Allows you to specify credentials to be used when your site is secured with [HTTP Basic Authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#basic_authentication_scheme). Otherwise, you might end up with a `401 Unauthorized` error running the command. * **`--uncached`** Ensure that only *uncached* pages are warmed. Perfect for when you just want to 'fill in the gaps' in your cache after some URLs were invalidated, without visiting every single URL in your website. This avoids unnecessary server load. * **`--include` and `--exclude`** Accepts a comma-separated list of URLs you'd like to be included/excluded in the warming process. Example: `--include='/about,/contact,/blog/*'` * **`--max-depth`** Allows you to specify the max depth of pages that should be warmed. For example with `--max-depth=1` it will visit pages like `/about` and `/products` but not `/products/cool-new-shoes-1` or `/any/other/path/that/is/too/deep`. * **`--max-requests`** Limits the number of requests made by the command. Likely makes the most sense to be used alongside the `--uncached` option. * **`--header`** Allows you to specify custom HTTP headers to be sent with each request. Can be used multiple times to set multiple headers. Useful for APIs, protected routes, or any scenario where custom headers are required. For example: `--header="Authorization: Bearer your_token" --header="X-Ignore-Cache: true"` You can find [practical examples](#custom-headers) of this parameter below. Depending on your site's setup, it might be a good idea to add this command to your deployment script. ### Concurrency You may configure the amount of concurrent requests when warming the static cache in your strategy. By default the pool will use `25`, but feel free to adjust it up or down based on your server's resources. ```php 'strategies' => [ 'full' => [ 'driver' => 'file', 'path' => public_path('static'), 'lock_hold_length' => 0, 'warm_concurrency' => 10, // [tl! highlight] ], ], ``` :::tip Lower the `warm_concurrency` to reduce the overhead and slow the process down, raise it to warm faster by using more CPU. ::: ### Queuing When you're using a queue driver other than `sync`, Statamic will push the warming out to the queue. If needed, you can explicitly tell Statamic which queue and queue connection should be used when warming the static cache: ```php // config/statamic/static_caching.php 'warm_queue' => env('STATAMIC_STATIC_WARM_QUEUE'), 'warm_queue_connection' => env('STATAMIC_STATIC_WARM_QUEUE_CONNECTION'), ``` ``` STATAMIC_STATIC_WARM_QUEUE=warming STATAMIC_STATIC_WARM_QUEUE_CONNECTION=database ``` ### Warming additional URLs Statamic will automatically warm pages for entries, taxonomy terms and any basic `Route::statamic()` routes. If you wish to warm additional URLs as part of the `static:warm` command, you may add a hook into your `AppServiceProvider`'s `boot` method: ```php use Statamic\Console\Commands\StaticWarm; class AppServiceProvider { public function boot() { StaticWarm::hook('additional', function ($urls, $next) { return $next($urls->merge([ '/custom-1', '/custom-2', 'https://different-domain.com/custom-3', ])); }); } } ``` When you're adding a lot of additional URLs, you may want to use a dedicated class instead: ```php use App\StaticWarmExtras; use Statamic\Console\Commands\StaticWarm; class AppServiceProvider { public function boot() { StaticWarm::hook('additional', function ($urls, $next) { return $next($urls->merge(StaticWarmExtras::handle())); }); } } ``` ### Custom headers The `--headers` option can be used in advanced scenarios to control how the static cache is warmed. Here are some practical examples: #### Bypassing cache for refreshes with Nginx If you have custom Nginx rules, you can check for a specific header (e.g., `X-Cache-Refresh: 1`) and bypass the `try_files` static cache, forcing a fresh request to the backend. For example: ```nginx location / { if ($http_x_cache_refresh = "1") { proxy_pass http://127.0.0.1:8000; # your statamic server break; } try_files $uri $try_location; } ``` Then, you can run: ``` php please static:warm --header="X-Cache-Refresh: 1" ``` #### Warming behind authentication If your site is protected by HTTP authentication or expects a specific header, you can use `--header` to provide the necessary credentials or tokens so the warm requests are not blocked. For example: ``` php please static:warm --header="Authorization: Bearer your_token" ``` This ensures the cache warming requests are accepted by your backend even when authentication is required. ### Warming behind Cloudflare Cloudflare's bot protection (particularly "Verified Bots" and "Bot Fight Mode") can block or challenge the outgoing requests that `static:warm` makes back to your own site, since those requests look like automated traffic. When this happens you'll typically see `403` responses, challenge pages, or silently failing warms. The fix is to allow your server's own public IP through Cloudflare's WAF before it hits any bot rules. Create a WAF custom rule: - **Field:** `IP Source Address` - **Operator:** `equals` - **Value:** your server's public IPv4 (and IPv6 if applicable) - **Action:** `Skip` → skip *All remaining custom rules*, *Managed Rules*, *Rate limiting rules*, and *Bot Fight Mode / Super Bot Fight Mode* If you're on a load-balanced or multi-node setup, add each origin IP. Once the rule is in place, `static:warm` requests will bypass bot challenges and complete normally. :::tip If you can't whitelist an IP (shared hosting, dynamic IPs), an alternative is to send a secret header with `--header="X-Warm-Secret: your-token"` and add a Cloudflare WAF rule that skips bot checks when that header is present. Keep the token out of source control. ::: ## Excluding Pages You may wish to exclude certain URLs from being cached. ```php return [ 'exclude' => [ 'class' => null, 'urls' => [ '/contact', // [tl! add] '/blog/*', // Excludes /blog/post-name, but not /blog [tl! add] '/news*', // Exclude /news, /news/article, and /newspaper [tl! add] ], ], ]; ``` Query strings will be omitted from exclusion rules automatically, regardless of whether wildcards are used. For example, choosing to ignore `/blog` will also ignore `/blog?page=2`, etc. :::tip Rather than excluding entire pages, you may consider using the [nocache tag](/tags/nocache) to keep parts of your page dynamic, like forms, listings, or randomized areas. ::: :::tip Another tip CSRF tokens will automatically be excluded from the cache. You don't even need to use a `nocache` tag for that. ([With some exceptions](#csrf-tokens)) ::: If you'd like to dynamically exclude URLs from being cached (for example: if you want to add a "Exclude from Cache" toggle to entries), you can create your own excluder class: ```php // config/statamic/static_caching.php return [ 'exclude' => [ 'class' => App\StaticCaching\CustomExcluder::class, // [tl! add] 'urls' => [], ], ]; ``` ```php // app/StaticCaching/CustomExcluder.php baseUrl; } public function getExclusions(): array { return $this->exclusions; } public function isExcluded(string $url): bool { // Your custom logic here. // Return `true` for any URLs you wish to be excluded. return false; } } ``` Alternatively, you may also prevent URLs from being cached by adding the `X-Statamic-Uncacheable: true` header to requests. ## Invalidation A statically cached page will be served until it is invalidated. You have several options for how to invalidate your cache. ### Time Limit When using the application driver, you may specify the `expiry` time in minutes in the `static_caching.php` config file. After this length of time, the next request will be served fresh. By leaving the expiry setting `null`, it will never expire, except when you manually run `php artisan cache:clear`. **The expiry option is not available when using the file driver.** The generated HTML files will be served before PHP ever gets hit, and there's just nothing we can do about that. ### When Saving When saving content, the corresponding item’s URL will be flushed from the static cache automatically. You may also set specific rules for invalidating other pages when content is saved. For example: ``` php return [ 'invalidation' => [ 'class' => null, 'rules' => [ 'collections' => [ 'blog' => [ 'urls' => [ '/blog', '/blog/category/*', '/', ], ], ], 'taxonomies' => [ 'tags' => [ 'urls' => [ '/blog', '/blog/category/*', '/', ], ], ], 'globals' => [ 'settings' => [ 'urls' => [ '/*' ], ], ], 'navigation' => [ 'links' => [ 'urls' => [ '/*' ], ], ], ], ], ]; ``` #### Explanation - “when an entry in the blog collection is saved, we should invalidate the `/blog` page, any pages beginning with `/blog/category/`, and the home page.” - “when a term in the tags taxonomy is saved, we should invalidate those same pages” - “when the settings global set is saved, we invalidate all urls” - “when the links navigation is saved, we invalidate all urls” You may add as many rules as you need. #### Invalidating the entire static cache You may also choose to invalidate the entire static cache by specifying `all`. ``` php return [ 'invalidation' => [ 'class' => null, 'rules' => 'all', // [tl! highlight] ], ]; ``` #### Using fields in invalidation rules You may even use fields from your entry or term's data in invalidation rules, with support for basic if statements! ```php 'collections' => [ 'pages' => [ 'urls' => [ '/{parent_uri}', '/offices/{office_slug}/*', '{{ if office_is_headquarters }}/corporate{{ /if }}', ], ], ], ``` As a bonus, you can also use `{parent_uri}` to invalidate the parent entry's URI. ### On a schedule If you have the scheduler running, Statamic will use the same set of rules mentioned above, but when scheduled entries are due to become active. For example, if you schedule an entry for Friday at 8am, and you have the scheduler running, appropriate pages will be invalidated just as if you had clicked saved on that entry at Friday at 8am. [Learn how to use the scheduler](/scheduling) ### Custom invalidator class You can also specify a custom invalidator class to **programmatically determine which URLs should be invalidated**. To achieve that, override or extend [the default invalidator class](https://github.com/statamic/cms/blob/01f8dfd1cbe304be1848d2e4be167a0c49727170/src/StaticCaching/DefaultInvalidator.php). ```php return [ 'invalidation' => [ 'class' => App\StaticCaching\CustomInvalidator::class, // [tl! highlight] 'rules' => [], ], ]; ``` It's worth noting that the container binding for the Default Invalidator won't be used now, so you'll need to bind it yourself in your `AppServiceProvider`: ```php use App\StaticCaching\CustomInvalidator; use Statamic\StaticCaching\Cacher; class AppServiceProvider { public function boot() { $this->app->bind(CustomInvalidator::class, function ($app) { return new CustomInvalidator( $app[Cacher::class], $app['config']['statamic.static_caching.invalidation.rules'] ); }); } } ``` In your class you can then define the logic that decides how URLs should get invalidated. ```php // app/StaticCaching/CustomInvalidator.php rules === 'all') { return $this->cacher->flush(); } $urls = []; // Invalidates entries from the `events` collection. if ($item instanceof Entry && $item->collectionHandle() === 'events') { $urls[] = $item->uri(); } // Flush the URLs we've added to the $urls array. if (count($urls) >= 1) { $this->cacher->invalidateUrls($urls); return; } // Otherwise, when the $urls array is empty, fallback to the default invalidation logic. parent::invalidate($item); } } ``` ### By force To clear the static file cache you can run `php please static:clear` (and/or delete the appropriate static file locations). ## Background Re-caching By default, when a page is invalidated, the cached item is deleted. This means the next page visitor will get a fresh version, which might be slow. To refresh the item rather than delete it, you may opt in to background re-caching. ```php 'background_recache' => true, ``` If you are using full-measure static caching, you will need to adjust your server rewrite rules. ```nginx if ($args ~* "live-preview=(.*)") { set $try_location @not_static; } if ($arg___recache = "YOUR-TOKEN") { # [tl! ++] set $try_location @not_static; # [tl! ++] } # [tl! ++] location / { try_files $uri $try_location; } ``` You can get the token by running the `static:recache-token` command: ```bash $ php please static:recache-token [INFO] Your token is: YOUR-TOKEN ``` Or, if you'd rather set it explicitly, you may do that: ```php 'recache_token' => 'no-one-will-guess-this', ``` ## File locations When using the file driver, the static HTML files are stored in the `static` directory of your webroot, but you can change it. ``` php return [ 'strategies' => [ 'full' => [ 'driver' => 'file', 'path' => public_path('static'), ] ] ]; ``` You will need to update your appropriate server rewrite rules. ## Query parameters By default, Statamic will ignore query parameters and cache each URL once. This is the recommended setting for most sites. However, if you wish to enable this behaviour so pages with different query parameters are cached separately (useful for pagination or displaying pages differently based on user input), you can do so: ```php return [ 'ignore_query_strings' => false, ]; ``` ### Allowed and disallowed query parameters If you're using half measure caching, you may specify which query parameters Statamic should include in it's "normalized" static caching URL. This is useful if you only want certain query parameters to be persisted in your cache: ```php 'allowed_query_strings' => [ 'page', ], ``` **For example:** if you allow the `page` query parameter, and visit `/blog?page=2&utm_medium=social`, Statamic will serve/write the cached page for `/blog?page=2`. You can also do the opposite, by specifying which query parameters should be excluded from the "normalized" static caching URL: ```php 'disallowed_query_strings' => [ 'fbclid', 'gclid', 'msclkid', 'utm_campaign', 'utm_content', 'utm_medium', 'utm_source', 'utm_term', ], ``` **For example:** if you disallow the UTM query parameters, and visit `/blog?page=2&utm_medium=social`, Statamic will serve/write the cached page for `/blog?page=2`. The `ignore_query_strings` option should be set to `false` in order for the `allowed_query_strings` & `disallowed_query_strings` to work. ## Multi-site When using static caching alongside [multi-site](/multi-site), some additional configuration is needed. ### Paths The `path` config option accepts an array, allowing you to define a different path for each site: ``` php return [ 'strategies' => [ 'full' => [ 'driver' => 'file', 'path' => [ 'english' => public_path('static') . '/domain.com/', 'french' => public_path('static') . '/domain.fr/', 'german' => public_path('static') . '/domain.de/', ], ], ], ]; ``` For sites with subdirectory URLs rather than separate domains, you should ensure that all sites with the same domain have the same path. For example: the `english` and `french` sites below are on the same domain, whereas `german` is on its own domain. ``` php return [ 'strategies' => [ 'full' => [ 'driver' => 'file', 'path' => [ 'english' => public_path('static') . '/domain.com/', 'french' => public_path('static') . '/domain.com/', 'german' => public_path('static') . '/domain.de/', ], ], ], ]; ``` _**Note:** You only need to configure paths when you're using full-measure static caching._ ### Rewrite rules When you have sites across multiple domains, you will need to modify the rewrite rules on your server to include the domain name. _**Note:** You only need to configure rewrite rules when you're using full-measure static caching._ #### Apache You should update the rewrites in your `.htaccess` file to include `%{HTTP_HOST}`: ``` htaccess RewriteCond %{DOCUMENT_ROOT}/static/%{HTTP_HOST}/%{REQUEST_URI}_%{QUERY_STRING}\.html -s RewriteCond %{REQUEST_METHOD} GET RewriteRule .* static/%{HTTP_HOST}/%{REQUEST_URI}_%{QUERY_STRING}\.html [L,T=text/html] ``` #### Nginx You should update the `try_files` line inside the `@static` block: ``` nginx location @static { try_files /static${uri}_$args.html $uri $uri/ /index.php?$args; # [tl! remove] try_files /static/${host}${uri}_$args.html $uri $uri/ /index.php?$args; # [tl! add] } ``` The `${host}` argument should correspond to the domains set up in the path. This will be dependant on the server. If you're running different environments and need to use caching for them, you should define the paths using an ENV variable that corresponds to each server domain. The path can be configured in the `static_caching` config: For example: ``` php 'strategies' => [ 'full' => [ 'driver' => 'file', 'path' => public_path('static') . '/' .env('APP_DOMAIN'), // [tl! focus] 'lock_hold_length' => 0, 'warm_concurrency' => 10 ], ], ``` and then on your server ``` # Production APP_DOMAIN=domain1.com # Dev APP_DOMAIN=domain1.devserver.com ``` #### IIS ``` xml ``` :::tip `{SERVER_NAME}` is used here instead of `{HTTP_HOST}` because `{HTTP_HOST}` may include the port. ::: ### Invalidation rules In the [invalidation rules array](#when-saving) explained above, the URLs are relative. If you are using sites with multiple domains, you should define URLs in additional domains using absolute URLs. Relative URLs will assume the first site's domain. ```php return [ 'invalidation' => [ 'rules' => [ 'collections' => [ 'blog' => [ 'urls' => [ '/blog', // [tl! **] 'https://domaintwo.com/articles', // [tl! **] ] ], ], ], ], ]; ``` :::tip Rather than hardcoding the domains, you could use a config key or a variable. ```php [ '/blog', $two.'articles', // [tl! **] ] ``` ::: ## Replacers When a page is being statically cached on the first request, or loaded on subsequent requests, they are sent through "replacers". Statamic includes two replacers out of the box. One will replace [CSRF tokens](#csrf-tokens), the other will handle [nocache](/tags/nocache) tag usages. A replacer is a class that implements a `Statamic\StaticCaching\Replacer` interface. You will be passed responses to the appropriate methods where you can adjust them as necessary. You can then enable your class by adding it to `config/statamic/static_caching.php`: ```php 'replacers' => [ CsrfTokenReplacer::class, NoCacheReplacer::class, MyReplacer::class, // [tl!++] ] ``` ### CSRF Tokens When using half measure, CSRF tokens will be replaced without any caveats. When using full measure, tokens will automatically be replaced in `` and `` tags where their value/content is the token. ``` ``` If you need to output a CSRF token in another place while using full measure, you'll need to use nocache tags. ``` {{ nocache }} {{# [tl!++] #}} {{ csrf_token }} {{ /nocache }} {{# [tl!++] #}} ``` ## Locks To prevent race conditions, the static cache middleware uses an atomic lock around the bits that write to the cache. If multiple requests for the same uncached URL come in at once, only the first one will render and write the page — the rest wait, and once the lock releases they get served the freshly cached response instead of redundantly writing it themselves. Cached responses are served _without_ acquiring the lock, so a hit is never blocked by a concurrent miss for a different URL. ### Lock store Locks use [Laravel's atomic cache locks](https://laravel.com/docs/cache#atomic-locks) on the [`static_cache` store](#custom-cache-store) if you've defined one, otherwise the default cache store. For horizontally-scaled setups (multiple app servers) you should point this at a shared driver like Redis or Memcached so the lock is visible across nodes. The default `file` driver only locks within a single server. ### Lock timeout Requests will wait up to 30 seconds for an in-progress request to finish caching the page. If that timeout is exceeded, you'll get a blank `503 Service Unavailable` response with a meta refresh that retries automatically. ### File write locks When using the `file` driver, there's a separate `lock_hold_length` option that controls how long (in seconds) a worker should retry while another worker holds the file write lock. Defaults to `0` — no retry, the second writer just bails. ```php 'strategies' => [ 'full' => [ 'driver' => 'file', 'path' => public_path('static'), 'lock_hold_length' => 0, // [tl! highlight] ], ], ``` You generally don't need to touch this — the middleware-level lock above already serializes writes for the same URL. Bump it up only if you're seeing contention from outside the middleware (e.g. multiple `static:warm` runs or external tooling writing to the same directory). ## Custom cache store Static caching leverages [Laravel's application cache](https://laravel.com/docs/cache) to store mappings of the URLs to the filenames, as well as the [locks](#locks) used by the middleware. To ensure proper invalidation of changes to your content, Statamic uses a cache store _outside_ of the default one. Otherwise, running the `php artisan cache:clear` command can lead invalidation to fail. The cache store can be customized in `config/cache.php`. ```php 'static_cache' => [ 'driver' => 'file', 'path' => storage_path('statamic/static-urls-cache'), ], ``` By default, running `php artisan cache:clear` won't clear Statamic's cache store. To do this, run `php please static:clear`. ================================================ FILE: content/collections/pages/structures.md ================================================ --- id: 3c34ef5c-781e-4a22-a09b-25f58bdb58a8 blueprint: page title: Structures intro: 'A structure is a hierarchy of items used to build navigation on the front-end of your site and optionally dictate the URL structure for entire collections.' template: page related_entries: - 2af9fc45-66d0-4ca5-9761-00017076144f - ed746608-87f9-448f-bf57-051da132fef7 --- ## Overview Structures are a flexible way to create hierarchies of different items. Statamic 2's "Pages" feature has been replaced by a "Structured Collection" (more on that in a bit). Each structure is a hierarchy of links and/or titles. These links may be to entries in one or more [collections](/collections), external URLs, or even anchor links in your content.
    A Statamic structure page tree A Statamic structure page tree
    Part of the structure of this very site.
    ## Two Flavors Structures come in two flavors, unlike Pringles. They can control the discrete URL pattern and order for a collection (like v2's Pages), or manage ad hoc navigation trees. And _just like_ Pringles — once you pop the top, you can't stop. ## Structured collections The first type of structure is for defining the URL structure for a collection. This would be the equivalent of "Pages" in Statamic v2. - An "orderable" collection will be using a Structure under the hood. - You will only be able to add entries from that collection. - The order and arrangement of the entries will dictate their URLs. - This will make `parent_uri` and `depth` variables available to the Collection's route. - You can only place an entry once. - You can create internal and external redirects. - The structure is stored on the collection itself, and its tree is stored in `content/trees/collections`. [Read more about using Structures to manage your Collections](/collections#ordering) ## Navigation (or "Navs") {#navigation} Freestyle navigation structures exist to manage a nav out of existing entries, as well as freeform links and text (non-link) elements. - You can reference entries, enter hardcoded URLs (internal or external), or enter simple text blocks (which can be used as section headers for dropdown navs, for example). - You can select which collections' entries will be available to choose from. - Any referenced entries will use the URLs defined by the collection, regardless of the position in the Structure. - You can place the same entry multiple times. - The structure is stored as a YAML file inside `content/navigation`, and its tree is stored in `content/trees/navigation`. [Read more about Navigation](/navigation) ## Templating You can work with the [nav tag](/tags/nav) to loop through and render your HTML with its links. ::tabs ::tab antlers ```antlers ``` ::tab blade ```blade ``` :: ## Tree The tree is what defines the structure. It contains an array of items, each of which is considered a "page". ``` yaml tree: - entry: id-of-about children: - entry: id-of-hobbies - entry: id-of-blog - title: Support children: - entry: id-of-contact - title: 'GitHub Repo' url: 'https://github.com/example/repo' ``` :::tip While you **can** edit a structure through the files, it's **much easier** to manage in Control Panel with a simple drag and drop interface. ::: Each page may have an optional `children` array which is itself another tree. You can repeat this pattern and go as deep as necessary. The `max_depth` setting on the Structure will prevent you from placing pages any deeper when using the Control Panel. - An entry reference should contain an `entry` key with a value of the entry's ID. The about, hobbies, blog, and contact pages in the snippet above are examples. - A hardcoded link* should contain a `url` key with either an internal or external URL. The `title` is optional. The GitHub page in the snippet above is an example. - Text* can just contain a `title`. The Support page above is an example. _\* Text and link branches are only available in Navs._ ================================================ FILE: content/collections/pages/tags.md ================================================ --- id: 75ce3bdc-03d0-4d6a-a2a5-d867d868d763 title: Tags blueprint: link redirect: url: '@child' status: 301 --- ================================================ FILE: content/collections/pages/taxonomies.md ================================================ --- id: 6a18eac8-6139-419c-9d64-a2c960ccc3cd blueprint: page title: Taxonomies intro: 'A taxonomy is a system of classifying data around a set of unique characteristics. Scientists have been using this system for years, grouping all living creatures into Kingdoms, Class, Species and so on. Taxonomies are the primary means for grouping content together by topic or a shared attribute.' related_entries: - 7202c698-942a-4dc0-b006-b982784efb03 - ba832b71-a567-491c-b1a3-3b3fae214703 - 3f5506d6-03e0-4fcf-b4e8-334c48d51f81 - 420f083d-99be-4d54-9f81-3c09cb1f97b7 --- ## Overview Taxonomies give you the ability to tag your entries and then fetch and sort all the entries that share any given tag. `Categories` and `tags` are probably the most common taxonomies, but you're not limited to those two. There are many useful taxonomies that can help group and sort your content. For example, `topic`, `color`, `genre`, and `size`. Practically speaking, taxonomies are very similar to [collections](/collections). They can have their own fields as defined by [blueprints](/blueprints) and also have their own URLs. Each entry in a taxonomy is often called a **term**. :::watch https://youtube.com/embed/vssXeEC118M Watch how to set up your first Taxonomy ::: ## Collections Each collection defines which taxonomies are part of its content model in their blueprint. Thus, taxonomies and their terms are connected to entries _through_ the collection in a strict relationship. Once you attach a taxonomy to a collection, the fields, variables, and routes are added automatically. Taxonomies can be attached to any number of collections but their terms are _global_, which means that any data stored on each term will be the same no matter the collection it's being related through. This is usually what you want, but if it isn't you can create additional taxonomies for specific collections. For example: `product_tags` in addition to `tags`. ## Blueprints Each taxonomy uses blueprints to define the available fields when creating and editing its terms. If you don't explicitly create a blueprint, your terms will have a basic set of fields: title, markdown content, slug, etc. Of course, you're able to create your own. If you create _more than_ one blueprint you'll be given the option to choose which one you want when creating a new term. ## Routing Taxonomy routes are automatically created for you **if the corresponding view exists**. :::tip URLs use slugs with dashes, and views use handles with underscores. ::: - **Global Taxonomy Details** - Display the details of the taxonomy, so you can list the terms. - Accessible at `/{taxonomy-slug}` (eg. `/tags`) - The `{taxonomy_handle}/index` view will be used (eg. `tags/index.antlers.html`) - **Global Term details** - Display the details of the term, so you can list the entries. - Accessible at `/{taxonomy-slug}/{term-slug}` (eg. `/tags/t-shirts`) - The `{taxonomy_handle}/show` view will be used. (eg. `tags/show.antlers.html`) For each taxonomy [assigned to a collection](#collections) you will also get these routes: - **Collection Taxonomy Details** - Display the details of the taxonomy, so you can list the terms. - Only terms that have been used in entries in the collection will be displayed. - Accessible at `/{collection-url}/{taxonomy-slug}` (eg. `/products/tags`) - The `{collection_handle}/{taxonomy_handle}/index` view will be used (eg. `products/tags/index.antlers.html`) - **Collection Term details** - Display the details of the term, so you can list the entries. - Only entries that exist in the collection will be displayed. - Accessible at `/{collection-url}/{taxonomy-slug}/{term-slug}` (eg. `/products/tags/t-shirts`) - The `{collection_handle}/{taxonomy_handle}/show` view will be used. (eg. `products/tags/show.antlers.html`) ## Term values and slugs A term **value** is how you might identify a term in your content. For example, “Star Wars”. A term **slug** is the URL-safe version, and is what Statamic uses internally to track terms, e.g. `star-wars`. The slug is created automatically based on a few rules. Let’s cover them now. How we slugify your terms: ``` yaml tags: - Star Wars - Tatooine - Droids We're Not Looking For ``` - The value `Star Wars` will be converted to lowercase, and all spaces and special characters will be replaced with hyphens: `star-wars`. - If a term with the slug `star-wars` already exists, the relation is made. - If no such term yet exists one will be created, and the entered value (`Star Wars`) will become the title. Titles are saved on a first-come, first-serve basis, which means consistency is important. If you enter `Star Wars` in one entry, and `star wars` in another, whichever term Statamic encounters first will be used as the title. To further clarify, `Star wars`, `star wars`, `StAr WaRS`, and `star-wars` are all treated as the same term. If case-sensitivity is important, you can add a `title` field to the taxonomy blueprint. ## Templating ### Views Taxonomies use the following view template naming convention: | Purpose | View | |---|---| | Taxonomy Index | `{taxonomy_name}/index` | | Single Term | `{taxonomy_name}/show` | | Taxonomy Index (for collection) | `{collection}/{taxonomy_name}/index` | | Single Term (for collection) | `{collection}/{taxonomy_name}/show` | For example, you would set up your "topics" index page in `resources/views/topics/index.antlers.html` and then a specific topic with a list of all entries inside it at `resources/views/topics/show.antlers.html`. The collection equivalents would automatically filter terms that have been associated to entries in that collection. ### Outputting terms Term values will be [augmented](/augmentation) into term objects and will have access to all data ``` yaml tags: - awesome - sauce ``` ::tabs ::tab antlers ```antlers {{ tags }} {{ title }}, {{ url }}, {{ slug }}, etc {{ /tags }} ``` ::tab blade ```blade @foreach ($tags as $tag) {{ $tag->title }}, {{ $tag->url }}, {{ $tag->slug }}, etc @endforeach ``` :: ``` Awesome, /tags/awesome, awesome, etc Sauce, /tags/sauce, sauce, etc ``` When the collection can be inferred, the `url` and `permalink` values will include the collection's URL. (eg. `/blog/tags/awesome` instead of just `/tags/awesome`) - ✅ Looping through tags on an entry's page. - ✅ Looping through tags while inside a collection tag pair. - ✅ Looping through terms in a taxonomy tag pair, using the collection parameter. - ❌ Looping through terms in a taxonomy tag pair, without specifying a collection. ### Listings and indexes When on a [taxonomy route](#routing), you can list the terms by using a `terms` tag pair. For example: ::tabs ::tab antlers ```antlers ``` :::tip You can replace the `terms` tag with the name of the taxonomy. eg. `{{ tags }}` or `{{ categories }}` ::: :::tip If your taxonomy name conflicts with a [tag](/tags), you will need to [disambiguate](/antlers#disambiguating-variables) it by using a dollar symbol (`$`). For example, if your taxonomy is named `section`, there is also a [tag named section](/tags/section). ``` {{ $section }}...{{ /$section }} ``` ::: ::tab blade ```blade ``` :::tip You can replace the `terms` tag with the name of the taxonomy. eg. `$tags` or `$categories` ::: :: ### Listing term entries When on a [term route](#routing), you can list the entries by using an `entries` tag pair. For example: ::tabs ::tab antlers ```antlers {{ entries paginate="5" }} {{ /entries }} ``` ::tab blade ```blade @php($results = $entries->paginate(5)) ``` :: ## Search indexes You can configure search indexes for your collections to improve the efficiency and relevancy of your users searches. Learn [how to connect indexes](/search#connecting-indexes). ================================================ FILE: content/collections/pages/testing.md ================================================ --- id: 15db07e8-6e83-4b6e-89bb-d050b5d2c823 blueprint: page title: Testing template: page nav_title: Testing intro: "There's only one thing better than manual testing... automated testing. Addons are scaffolded with PHPUnit test suites out-of-the-box. Learn how to write & run tests." --- When you create an addon with the `php please make:addon` command, Statamic will automatically scaffold the necessary files for a PHPUnit test suite: ``` files theme:serendipity-light tests/ ExampleTest.php TestCase.php phpunit.xml ``` ## The `TestCase` The `TestCase` class extends Statamic's built-in `AddonTestCase` which is responsible for booting your addon's service provider, amongst other things. Under the hood, Statamic's `AddonTestCase` extends [Orchestra Testbench](https://github.com/orchestral/testbench)'s `TestCase` class. Testbench allows you to test against a *real* Laravel application. If you need to change any config settings for your test suite, like enabling Statamic Pro or configuring the REST API, add a `resolveApplicationConfiguration` method to your `TestCase`: ```php protected function resolveApplicationConfiguration($app) { parent::resolveApplicationConfiguration($app); $app['config']->set('statamic.editions.pro', true); $app['config']->set('statamic.api.resources', [ 'collections' => true, 'navs' => true, 'taxonomies' => true, 'assets' => true, 'globals' => true, 'forms' => true, 'users' => true, ]); } ``` ## Writing Tests All of your tests should extend your addon's `TestCase` class, like so: ```php assertTrue(true); } } ``` For more information on writing tests, please review the [Laravel Testing Documentation](https://laravel.com/docs/13.x/testing). ### The Stache During tests, any Stache items (like entries, terms, global sets) will be saved inside your `tests/__fixtures__` directory. However, most of the time, you'll probably want to blow away old content between test runs. To do this, you may add the `PreventsSavingStacheItemsToDisk` trait to your tests: ```php use Statamic\Testing\Concerns\PreventsSavingStacheItemsToDisk; // [tl! focus] class ExampleTest extends TestCase { use PreventsSavingStacheItemsToDisk; // [tl! focus] // ... } ``` ### Update Scripts To test an [update script](/addons/building-an-addon#update-scripts), import the `RunsUpdateScripts` trait and call `$this->runUpdateScript()` with your script class. ```php use Statamic\Testing\Concerns\RunsUpdateScripts; // [tl! focus] class ExampleTest extends TestCase { use RunsUpdateScripts; // [tl! focus] #[Test] public function it_does_what_it_needs_to_do() { $this->runUpdateScript(YourUpdateScript::class); // [tl! focus] } // ... } ``` :::warning The `RunsUpdateScripts` trait is only available in Statamic v6.3.0 and above. You may need to bump your minimum supported version to use it. ::: ### Inertia.js The Control Panel is powered by [Inertia.js](https://inertiajs.com), allowing Statamic to render pages as Vue components instead of traditional Blade views. To assert an Inertia response, use the `->assertInertia()` macro: ```php use Inertia\Testing\AssertableInertia as Assert; $this ->get(cp_route('my-addon.index')) ->assertInertia(fn (Assert $page) => $page ->component('my-addon::Foo') ->has('message'); ); ``` For more details, see the [Inertia.js testing documentation](https://inertiajs.com/docs/v2/advanced/testing). ## Running Tests Once you've written some tests, you can run them using `phpunit`: ```bash ./vendor/bin/phpunit ``` You may run a specific test by passing the `--filter` argument: ```bash # Runs all tests in the CheckoutTest ./vendor/bin/phpunit --filter=CheckoutTest # Runs the specific user_cant_checkout_without_payment test ./vendor/bin/phpunit --filter=user_cant_checkout_without_payment # Runs all tests with checkout in their name. ./vendor/bin/phpunit --filter=checkout ``` ### GitHub Actions When you're using GitHub to store your addon's source code, you can take advantage of GitHub Actions so your addon's tests are run whenever you push to your repository or whenever a pull request is submitted. Running tests on GitHub Actions (or any CI platform for that matter) saves you running the tests locally after every change and also means you can run your addon's tests against multiple PHP & Laravel versions. For ease of use, here's an example GitHub Actions workflow: ```yaml name: Test Suite on: push: pull_request: jobs: php_tests: strategy: matrix: php: [8.3, 8.4, 8.5] laravel: [12.*, 13.*] os: [ubuntu-latest] name: ${{ matrix.php }} - ${{ matrix.laravel }} runs-on: ${{ matrix.os }} steps: - name: Checkout code uses: actions/checkout@v1 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick - name: Install dependencies run: | composer require "laravel/framework:${{ matrix.laravel }}" --no-interaction --no-update composer install --no-interaction - name: Run PHPUnit run: vendor/bin/phpunit ``` ================================================ FILE: content/collections/pages/tips.md ================================================ --- id: b6c40452-8da0-4f03-a53c-714cc3338b9d blueprint: page title: 'Tips & Tricks' template: tips/index --- Here's a collection of little tips & tricks to help you on your way. ================================================ FILE: content/collections/pages/toast-notifications.md ================================================ --- title: Toast Notifications intro: Simple notification messages that "pop" into the screen like toast popping out of a toaster. id: 52af4663-ebbd-467c-8659-9c7bb94cb7cc --- You may display simple toast notifications through the `$toast` instance method. ``` js this.$toast.info('message'); // Basic message this.$toast.success('message'); // Green success this.$toast.error('message'); // Red error this.$toast.success('message', { duration: 3000 }); // Auto-disappear after this many milliseconds ``` You may also trigger these from the server using the `Toast` facade. ```php use Statamic\Facades\CP\Toast; Toast::info('message'); Toast::success('message'); Toast::error('message'); Toast::info('message')->duration(3000); ``` You don't have to return them in a response, simply calling them is enough. They will automatically be routed through the response into JavaScript. ================================================ FILE: content/collections/pages/troubleshooting.md ================================================ --- id: cd0428cf-2c4a-43b9-8e61-dd8123799ed8 blueprint: page title: Troubleshooting intro: 'Here are some common pitfalls.' updated_by: 3a60f79d-8381-4def-a970-5df62f0f5d56 updated_at: 1632426159 template: troubleshooting/index --- ================================================ FILE: content/collections/pages/ubuntu.md ================================================ --- id: c0009fa6-0f8f-4b45-8d65-0cb784d07031 blueprint: page title: 'How to Install Statamic on Ubuntu' breadcrumb_title: Ubuntu intro: 'A full walk-through for installing, configuring and running Statamic on an Ubuntu server, perfect for production use.' parent: ab08f409-8bbe-4ede-b421-d05777d292f7 --- ## Prerequisites To install Statamic on an Ubuntu instance you will need the following: - An Ubuntu 24.04 VPS with root access enabled or a user with sudo privileges (you can follow our [Digital Ocean](/installing/digital-ocean) or [Linode](/installing/linode) guides to get yours set up) - A server with at least 1GB memory - A valid domain name pointed to your server and SSL certificate in place - PHP 8.3+ ## Update Packages If this is the first time using `apt` in this session, you should sure package lists and installed packages are up to date. ``` shell sudo apt-get update sudo apt-get upgrade ``` ## Install PHP & Required Modules ``` shell sudo apt install php-common php-fpm php-json php-mbstring zip unzip php-zip php-cli php-xml php-tokenizer php-curl php-gd -y ``` ## Install Composer Install Composer with the following command: ``` curl curl -sS https://getcomposer.org/installer | php ``` Next, you need to move the `composer.phar` file to a globally accessible directory and update its permissions. ``` shell sudo mv composer.phar /usr/local/bin/composer sudo chmod +x /usr/local/bin/composer ``` :::tip Do not run `composer` as root. If you followed the steps for creating a Digital Ocean droplet, you will need to create a new user to run `composer` as. ::: Now you can check to make sure Composer is installed and configured correctly by running this command: ``` shell composer ``` ## Install Statamic CLI Next up, let's install the Statamic CLI. To do this, run the following command in your terminal: ``` shell composer global require statamic/cli ``` Upon installation, you can now use the `statamic new` command to spin up fresh Statamic sites with a CLI setup wizard 🧙‍♂️ to guide you through a variety of settings and options. Our CLI is essentially a super fancy wrapper around the `composer create-project` command. You can choose to not install it, but only at your own annoyance. ## Install & Configure Nginx Next, install [Nginx](https://nginx.com) with the following command: ``` shell sudo apt install nginx -y ``` After the install completes, Nginx will start automatically. You can verify the service by running the following the command: ``` shell sudo systemctl status nginx ``` ## Create a new Statamic Application Now we'll create a new Statamic application using the `statamic new` command. First, navigate to the default Nginx root directory: ``` shell cd /var/www/html ``` Next, run the following install command (you may need to update your [$PATH](/knowledge-base/troubleshooting/command-not-found-statamic)): ``` shell statamic new example.com ``` :::tip If you run into a TTY error like `TTY mode requires /dev/tty to be read/writable`, you can simulate TTY by calling `script` and using the `--no-interaction` flag. ``` script -q -c "statamic new --no-interaction example.com" ``` ::: ## Set Permissions Once Statamic is installed, you'll need to grant appropriate permissions to the non-root user so Statamic and Laravel can write to the necessary system directories. ``` shell sudo chmod -R 755 /var/www/example.com sudo chown -R www-data:www-data /var/www/html/example.com ``` Next, let's set up the minimum recommended config file to serve your site. Create a new file with `sudo vim` (or your command line editor of choice) at `/etc/nginx/sites-available/example.com`, making sure to replace `example.com` everywhere with your desired domain. ```php server { listen 80; server_name example.com; // [tl! highlight:1] root /var/www/html/example.com/public; add_header X-Frame-Options "SAMEORIGIN"; add_header X-XSS-Protection "1; mode=block"; add_header X-Content-Type-Options "nosniff"; index index.html index.htm index.php; charset utf-8; set $try_location @static; if ($request_method != GET) { set $try_location @not_static; } if ($args ~* "live-preview=(.*)") { set $try_location @not_static; } location / { try_files $uri $try_location; } location @static { try_files /static${uri}_$args.html $uri $uri/ /index.php?$args; } location @not_static { try_files $uri /index.php?$args; } location = /favicon.ico { access_log off; log_not_found off; } location = /robots.txt { access_log off; log_not_found off; } error_page 404 /index.php; location ~ \.php$ { fastcgi_pass unix:/var/run/php/php8.3-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; include fastcgi_params; } location ~ /\.(?!well-known).* { deny all; } } ``` :::tip You should ensure that the PHP path matches the version of PHP-FPM you have installed. For example: the example config above specifies `fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;`, but if you update all packages, you may end up with a later version. ::: You can confirm that the configuration doesn’t contain any syntax errors with the following command: ``` shell sudo nginx -t ``` You should see output similar to this: ``` shell # Output nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful ``` Symlink the newly created config file and reload Nginx to apply these changes: ``` shell sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/ sudo systemctl reload nginx ``` Now visit your IP Address or `https://example.com` (but like the actual domain) if you've pointed your domain's A Record and you should see the Statamic landing page.
    Statamic Welcome Screen
    If you see this, you have just won.
    ## Conclusion That's pretty much it. Time for you to take it from here. If this is your production server, you'll probably want to add cache expiry headers and so on, but those rules and what you cache to the browser are all up to you. There are many other variables at play when building your own server. It's unrealistic to think we've covered them all here, or that this article won't get out of date at times as things change. If you run into issues not covered here, you may want to read through [this discussion](https://github.com/statamic/cms/discussions/10487) where several folks work through many edge cases happening all at once. ================================================ FILE: content/collections/pages/ui-components.md ================================================ --- id: f38a3e52-10ba-4bfa-9298-0c95b324c662 title: 'UI Components' nav_title: 'UI Components' blueprint: link redirect: url: 'https://ui.statamic.dev' status: 301 --- ================================================ FILE: content/collections/pages/updating-a-starter-kit.md ================================================ --- id: 512cbb65-0091-47e2-a3b6-c42aae64349e blueprint: page title: 'Updating a Starter Kit' intro: | Starter Kits aren't all designed to be updatable, but when they are you'll need to know how. template: page nav_title: Updating --- ## Not all starter kits are updatable As their name implies, starter kits were originally intended to be a way to "start" a site. Once installed, you're on your own and can customize as you see fit. However, some kits _are_ built in a way that they can be updated. Updatable kits will be noted on the Statamic Marketplace. For example, the [Podcaster](https://statamic.com/starter-kits/statamic/podcaster) Kit is designed in an opinionated way, with settings for you to tweak brand colors. If a CSS or JavaScript bug is encountered one day, the developer of the Kit can fix it and you can simply pull down the update into your site. ## Performing updates When initially installing an updatable Starter Kit, it will be left as a dependency in your `composer.json` file. To update, you will need to update via Composer: ```shell composer update ``` (Or `composer update vendor/starter-kit-name` to avoid updating everything else.) Additionally, you may need to run additional commands to re-compile or publish assets. These should be explained in the Starter Kit's documentation, but would typically be something like this: ```shell php artisan vendor:publish --tag=starter-kit-name npm run dev ``` ================================================ FILE: content/collections/pages/updating.md ================================================ --- title: Updating id: e6f05019-6bdd-488e-ba45-39ae7ea5cee7 blueprint: page intro: Updates are handled by [Composer](https://getcomposer.org/), PHP's dependency manager. We recommend running all updates locally (not on production) via the command line and deploying those changes to production after verifying everything still works as it should. --- :::best-practice We recommend running **all updates** locally to eliminate downtime and any possibility of an unexpected change or timeout affecting your site. ::: ## Composer If you installed with Composer, you can update your installation with the following command: ``` composer update statamic/cms --with-dependencies ``` Note: You may prefer to run `composer update` to update _all_ of your dependencies. ## Statamic CLI If you installed with [Statamic CLI](/cli), you can update your installation with the following command: ``` statamic update ``` ## Control Panel The Control Panel will inform you when updates are available. From within the **Tools → Updates** section, Statamic will provide you with the appropriate Composer commands to run. If you choose to install a non-latest version, your `statamic/cms` Composer version dependency will be fixed to whichever explicit version you choose. To go back to a constraint-style version, you'll need to update your `composer.json` file. For example, if you chose `v6.0.1` in the control panel, this will be your Composer constraint. ```json { "require": { "statamic/cms": "6.0.1", } } ``` To go back to a more traditional version range constraint, you may want to replace it with this: ```json { "require": { "statamic/cms": "^6.0", } } ``` ## Major upgrades Upgrading between major Statamic versions sometimes involves extra manual steps. Check out [these guides](/upgrade-guide) for further details. ================================================ FILE: content/collections/pages/upgrade-guide.md ================================================ --- title: 'Upgrade guide' intro: How to upgrade between various versions of Statamic. template: page id: f12f8ba3-19ff-48cb-a07b-653b05082d7e blueprint: page --- - [5.0 to 6.0](/getting-started/upgrade-guide/5-to-6) - [4.0 to 5.0](/getting-started/upgrade-guide/4-to-5) - [3.4 to 4.0](/getting-started/upgrade-guide/3-4-to-4-0) - [3.3 to 3.4](/getting-started/upgrade-guide/3-3-to-3-4) - [3.2 to 3.3](/getting-started/upgrade-guide/3-2-to-3-3) - [3.1 to 3.2](/getting-started/upgrade-guide/3-1-to-3-2) - [3.0 to 3.1](/getting-started/upgrade-guide/3-0-to-3-1) - [2.x to 3.x](/getting-started/upgrade-guide/v2-to-v3) - [Bard 1 to 2](/getting-started/upgrade-guide/bard-v1-to-v2) - [Vue 2 to 3](/getting-started/upgrade-guide/vue-2-to-3) If you intend to **upgrade Laravel itself**, please refer to its [upgrade guide](https://laravel.com/docs/upgrade). You can also (semi-)automate the Laravel upgrade using [Laravel Shift](https://laravelshift.com) which is by far the most _rad_ way to upgrade. ================================================ FILE: content/collections/pages/users.md ================================================ --- id: 6b691e04-8f28-4eb2-8288-b61433883fe4 blueprint: page title: Users intro: 'Users are the member accounts to your site or application. What a user can do with their account is up to you. They could have limited or full access to the Control Panel, a login-only area of the front-end, or even something more custom by tapping into Laravel.' template: page pro: true related_entries: - 878f0dd7-2d31-479c-b58d-bc60685fa7d2 - 748f88ce-85f6-491b-8e9c-fa2b1895be31 - 4c3f5caa-a861-4ffd-a856-1692cafeb870 - 1ee69ba0-2fa4-4155-9b8d-82536ce95f99 - 55993382-c928-48d0-8559-c88b226d4657 --- ## Overview The most common and obvious reason users exist is to have the means to access the Control Panel and manage the content of your site. But there is so much more a user can do, if you so desire.
    List of Statamic Control Panel users List of Statamic Control Panel users
    Why hasn't the Hoff logged in? And why is he inpersonating Jason?
    ## Creating users The easiest way to create your **first user** is by running `php please make:user` terminal command. After entering basic information, setting a password, and saying `yes` to [super user](#super-users), you can log into the control panel at `example.com/cp`. :::watch https://youtube.com/embed/KuiPocGq3L8 Watch a new user being born. 🐣 ::: You can also [create users by hand](/tips/creating-users-by-hand) in a YAML file if you'd prefer, or don't have access to the command line. And don't worry, the password field will automatically get encrypted as soon as Statamic spots it. ### New user invitations When creating users in the Control Panel you can send email invitations to help guide those users into activating their accounts and signing in for the first time. You can even customize a lovely little welcome message for them.
    A user invitation screen A user invitation screen
    An opportunity for a knock knock joke, perhaps?
    :::tip Be sure to [configure the email driver](/email) so those emails actually go out. ::: ## User fields You're more than welcome — encouraged even — to customize what fields and information you'd like to store on your users. For example, you could store author bios and social media links to be used in articles on your front-end. To customize these fields, edit the included `user` [blueprint](/blueprints) and configure it however you'd like. ## Permissions A User by itself has no permission to access or change any aspect of Statamic. It takes explicit permissions for a user to access the control panel, create, edit, or publish content, create users, and so on. Permissions are grouped into **roles**, and are very simple to manage in the Control Panel and are stored in `resources/users/roles.yaml`. In turn, **roles** are attached directly to individual users or [user groups](#user-groups). ### Statamic's native permissions {#native-permissions} | Permission | Handle | | --------------------------------------------------------- | -------------------------------------------- | | Access the Control Panel | `access cp` | | Configure Sites | `configure sites` | | Configure Fields | `configure fields` | | Configure Form Fields | `configure form fields` | | Manage Preferences | `manage preferences` | | Access site | `access {site} site` | | Create, edit, and delete collections | `configure collections` | | View entries | `view {collection} entries` | | ↳ Edit entries | `edit {collection} entries` | |   ↳ Create entries | `create {collection} entries` | |   ↳ Delete entries | `delete {collection} entries` | |   ↳ Publish entries | `publish {collection} entries` | |   ↳ Reorder entries | `reorder {collection} entries` | |   ↳ Edit other author's entries | `edit other authors {collection} entries` | |     ↳ Publish other author's entries | `publish other authors {collection} entries` | |     ↳ Delete other author's entries | `delete other authors {collection} entries` | | Create, edit, and delete navs | `configure navs` | | ↳ View nav | `view {nav} nav` | |   ↳ Edit nav | `edit {nav} nav` | | Create, edit and delete global sets | `configure globals` | | Edit global variables | `edit {global} globals` | | Create, edit and delete taxonomies | `configure taxonomies` | | View terms | `view {taxonomy} terms` | | ↳ Edit terms | `edit {taxonomy} terms` | |   ↳ Create terms | `create {taxonomy} terms` | |   ↳ Delete terms | `delete {taxonomy} terms` | | Configure asset containers | `configure asset containers` | | View asset container | `view {container} assets` | | ↳ Upload assets | `upload {container} assets` | | ↳ Edit folders | `edit {container} folders` | | ↳ Edit assets | `edit {container} assets` | |   ↳ Move assets | `move {container} assets` | |   ↳ Rename assets | `rename {container} assets` | |   ↳ Delete assets | `delete {container} assets` | | View users | `view users` | | ↳ Edit users | `edit users` | |   ↳ Create users | `create users` | |   ↳ Delete users | `delete users` | |   ↳ Change passwords | `change passwords` | |   ↳ Assign user groups | `assign user groups` | |   ↳ Assign roles | `assign roles` | | Edit user groups | `edit user groups` | | Edit roles | `edit roles` | | Impersonate users | `impersonate users` | | View updates | `view updates` | | Configure forms | `configure forms` | | View form submissions | `view {form} form submissions` | |   ↳ Delete form submissions | `delete {form} form submissions` | | Configure addons | `configure addons` | | Edit addon settings | `edit {addon} settings` | | Access utility | `access {utility} utility` | | Resolve Duplicate IDs | `resolve duplicate ids` | | View GraphQL | `view graphql` | ### Author permissions Author permissions are a little bit special. They determine the control users can have over their own entries or those created by other authors. :::warning Important! This feature only has any effect if your entry blueprint has an `author` field. If you don't already have an `author` field, this functionality is not available. ::: ### Site permissions When using the [multi-site](/multi-site) feature, Statamic will check for appropriate site permissions in addition to whatever it's checking. For example, when you try to edit a `blog` entry in the `french` site, Statamic will check if you have both the `edit blog entries` and `access french site` permissions. ### Super users Super Admin accounts are special accounts with **access and permission to everything**. This includes things reserved only for super users like the ability to _create more super users_. It's important to prevent the robot apocalypse and this is an important firewall. We're just doing our part to save the world. ## User groups User groups allow you to attach roles, include users, thereby assign all the corresponding permissions automatically. This approach is much simpler than assigning roles individually if you have a lot of users. User groups are stored in `resources/users/groups.yaml`. ## Password resets Let's face it. People forget their passwords. A lot, and often. Statamic supports password resets. For users with Control Panel access, the login screen (found by default at `example.com/cp`) already handles this for you automatically. You can also create your own password reset pages for front-end users by using the [user:forgot_password_form](/tags/user-forgot_password_form) tag. The user will receive an email with a temporary, single-use token allowing them to set a new password and log in again. ## Password validation By default, passwords need to be 8 characters long. If you'd like to customize the default rules, you can use the `Password` rule object. (Requires at least Laravel 8.43). These rules will be used when creating passwords throughout Statamic. In the `make:user` command, in the `user:register_form` tag, or during the password activation/reset flows. If you create the password by hand in user yaml files, the rules will be bypassed. You can drop this into your `AppServiceProvider`'s `boot` method. ```php use Illuminate\Validation\Rules\Password; public function boot() { Password::defaults(function () { return Password::min(16); }); } ``` Consult the [Laravel documentation](https://laravel.com/docs/13.x/validation#validating-passwords) to see all the available methods for customizing the password rule. ## Storing user records {#storage} While users are stored in files by default — like everything else in Statamic — they can also be located in a database or really anywhere else. Here are links to articles for the different scenarios you may find yourself in. - [Storing Laravel Users in Files](/tips/storing-laravel-users-in-files) - [Storing Users in a Database](/tips/storing-users-in-a-database) - [Custom User Storage](/tips/storing-users-somewhere-custom) - [Using an Independent Auth Guard](/tips/using-an-independent-authentication-guard) ## Avatars Each user account has an avatar field named `avatar`. By default it's an [Assets Field](/fieldtypes/assets) that falls back to the user's initials. This avatar is used throughout the Control Panel to represent the user when the context is important. For example, on your user dropdown menu, as an entry's "Author", or while using [Real Time Collaboration](https://github.com/statamic/collaboration).
    A user's avatar in the control panel global header A user's avatar in the control panel global header
    Behold — an avatar! Little SNL joke there, for anyone in the mood
    ## Ordering By default, users are ordered alphabetically by their email. However, if you wish, you can change the field and direction used to order users in the Control Panel and when returned with the [`{{ users }}`](/tags/users) tag. ```php // config/statamic/users.php 'sort_field' => 'email', 'sort_direction' => 'asc', ``` ## Language preference Each user can have their own preferred language in the Control Panel. Head to your preferences area by clicking on the ⚙️ gear/cog icon in the global header and then go to **Preferences**. You can set the language for _everyone_ by going to **Default**, or you can set by Role or just the current user (yourself) with **Override For User**.
    User Language Preferences User Language Preferences
    Last we checked, Statamic has been translated into a lot of languages.
    ## Impersonate users Statamic gives you the ability to impersonate users via the Control Panel. This lets you see the Control Panel and front end of your site through the eyes of the user you chose. This is pretty neat if certain content or capabilities are limited through roles and permissions and you want to test those things. It saves quite some time since there's no need to manually sign out and in again with a different user anymore.
    List view of Statamic Control Panel users with a dropdown showing various options, one of them being 'Start Impersonation' List view of Statamic Control Panel users with a dropdown showing various options, one of them being 'Start Impersonation'
    Masquerade as someone else 🎭
    You can configure impersonation in `config/statamic/users.php`, like setting the redirect destination after starting impersonation or disabling it. Additionally, there is a dedicated `impersonate users` permission that you can assign to roles and users to allow or disallow them using this feature. ## OAuth In addition to conventional user authentication, Statamic also provides a simple, convenient way to authenticate with OAuth providers through [Laravel Socialite](https://github.com/laravel/socialite). Socialite currently supports authentication with Facebook, Twitter, LinkedIn, Google, GitHub, GitLab and Bitbucket, while dozens of additional providers are available though [third-party Socialite Providers](https://socialiteproviders.netlify.com/). Learn how to [configure OAuth](/oauth) on your site. ## Two-Factor Authentication Statamic includes first-party support for two-factor authentication (2FA), providing an extra layer of account security. Once enabled, users must enter a time-based one-time password (TOTP) from an authenticator app — like Google Authenticator or 1Password — alongside their password when logging in. To enable 2FA, head to your **Profile** in the Control Panel. Scan the QR code with your authenticator app, enter the generated code, and you’re set. You’ll also receive a set of recovery codes — store these somewhere safe in case you lose access to your authenticator app. 2FA is optional by default, but you can enforce it for specific roles via configuration: ```php // config/statamic/users.php 'two_factor_enforced_roles' => [ // Enforce for everyone '*', // Enforce for super users 'super_users', // Enforce for a specific role 'marketing_managers', 'user_admin', ], ``` You may also disable 2FA altogether by setting `STATAMIC_TWO_FACTOR_ENABLED=false` in your `.env` file. :::warning Statamic uses your `APP_KEY` to encrypt the two-factor authentication secret and recovery codes. You may run into issues with two-factor authentication if you have different `APP_KEY` values between environments *and* they share the same users (eg. you're tracking users in Git). You may want to disable 2FA locally in this case. ::: ### Frontend Two-Factor Authentication Users who authenticate through your site's frontend (via [`{{ user:login_form }}`](/tags/user-login_form)) can also set up and challenge 2FA without ever touching the Control Panel. Statamic ships a set of tags for building those pages yourself: - [`{{ user:two_factor_challenge_form }}`](/tags/user-two_factor_challenge_form) — the code verification form shown during login - [`{{ user:two_factor_enable_form }}`](/tags/user-two_factor_enable_form) — step 1 of setup, generates the secret - [`{{ user:two_factor_setup_form }}`](/tags/user-two_factor_setup_form) — step 2 of setup, displays the QR code and confirms the code - [`{{ user:disable_two_factor_form }}`](/tags/user-disable_two_factor_form) — lets users turn 2FA off - [`{{ user:two_factor_recovery_codes }}`](/tags/user-two_factor_recovery_codes) and [`{{ user:reset_two_factor_recovery_codes_form }}`](/tags/user-reset_two_factor_recovery_codes_form) — show and regenerate recovery codes - [`{{ user:two_factor_enabled }}`](/tags/user-two_factor_enabled) — a boolean for conditionally rendering the above When a user with 2FA enabled signs in on the frontend, Statamic redirects them to a challenge page. When 2FA is enforced for the user's role and they haven't set it up, Statamic redirects them to a setup page. Point these redirects at your own pages with the following config keys: ```php // config/statamic/users.php 'two_factor_challenge_url' => '/account/2fa/challenge', 'two_factor_setup_url' => '/account/2fa/setup', ``` Leave either value `null` to use Statamic's built-in page for that step. Control Panel flows are unaffected — they always use their own pages. ## Passkeys Statamic supports **passkeys** as a secure alternative to email-and-password logins. Passkeys are a passwordless authentication method built on WebAuthn and are supported by most modern operating systems and password managers. On macOS, iOS, and iPadOS, for example, you can sign in using Touch ID or Face ID. To add a passkey for the Control Panel, log in and visit your **profile**, where passkeys are managed from the Actions dropdown.
    Actions dropdown on the user profile page Actions dropdown on the user profile page
    Click **Create Passkey** and follow the prompts to complete setup. Once a passkey has been added, you can use it to sign in without entering your email address and password.
    Passkey button on sign in page Passkey button on sign in page
    Passkey behaviour, including whether password logins are still allowed for users with passkeys and whether “remember me” applies when logging in with a passkey, can be configured in `config/statamic/webauthn.php`. ## Rate limiting Statamic's authentication and passkey endpoints are rate limited by IP address to help protect against brute force attacks. The defaults apply to both the front-end and Control Panel: | Limiter | Default | Routes | | --- | --- | --- | | `statamic.auth` | 4 per minute | Front-end login, register, password email, password reset | | `statamic.cp.auth` | Inherits `statamic.auth` | Control Panel login, password email, password reset | | `statamic.passkeys` | 30 per minute | Front-end passkey authentication | | `statamic.cp.passkeys` | Inherits `statamic.passkeys` | Control Panel passkey authentication | You can customize any of these limits by redefining the named rate limiter in your `AppServiceProvider`'s `boot` method: ```php use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Http\Request; use Illuminate\Support\Facades\RateLimiter; public function boot() { RateLimiter::for('statamic.auth', function (Request $request) { return Limit::perMinute(10)->by($request->ip()); }); } ``` Overriding `statamic.auth` will affect both the front-end and Control Panel buckets unless you also define a separate `statamic.cp.auth` limiter. The same inheritance applies to `statamic.passkeys` and `statamic.cp.passkeys`. Consult the [Laravel documentation](https://laravel.com/docs/13.x/routing#rate-limiting) to learn more about defining rate limiters. ================================================ FILE: content/collections/pages/utilities.md ================================================ --- title: Utilities id: aa6e0a79-9d3f-493b-92c9-df4d2257bc64 intro: Utilities are simple tools with their own views, routes, navigation items, and permissions. --- ## What's a utility? A utility is really just a route or two with a view, injected into the _Utilities_ area of the control panel, and wrapped up with a permission. You _could_ make the same thing by wiring up the individual parts, but creating a utility is a shortcut. To get an idea for what a utility is, take a look at the utilities Statamic ships with: - A page for viewing cache information and a button to clear it. - A page to list PHP settings from `phpinfo()`. - A page letting you clear search indexes. - A page to view email configuration and send a test. ## Creating a utility Registering a utility will give you a route, nav item, and a permission for free. In a service provider's `boot` method, you can register a utility with the `Utility` facade. Start with `Utility::register()` with the handle of the utility, then chain as many methods as you want. Make sure to surround any utility registrations in a `Utility::extend` closure. ``` php use Statamic\Facades\Utility; public function boot() { Utility::extend(function () { Utility::register('data_wangjangler') ->inertia('my-addon::DataWangjangler', fn ($request) => [ 'items' => Item::all(), ]); }); } ``` The first argument is the name of [an Inertia page component](/control-panel/css-javascript#inertia), and the second is an optional closure that returns props for the component. You'll need to register the Inertia page using `Statamic.$inertia.register()`: ``` js import DataWangjangler from './components/DataWangjangler.vue'; Statamic.booting(() => { Statamic.$inertia.register('my-addon::DataWangjangler', DataWangjangler); }); ``` Then create your Vue component: ``` vue ``` :::tip For help setting up Vite and Vue in your project, please see the [Vite Tooling](/addons/vite-tooling) (for addons) or [CSS & JavaScript](/control-panel/css-javascript#inertia) guides. ::: ## Customizing the navigation and card You can customize the nav item, description, icon, and other details on the index listing by chaining the corresponding methods. The `icon()` method accepts the name of an [icon included in Statamic](https://ui.statamic.dev/?path=/docs/components-icon--docs#available-icons), or an SVG string containing a custom icon (be sure to use `fill="currentColor"`): ``` php use Statamic\Facades\Utility; public function boot() { Utility::extend(function () { Utility::register('data_wangjangler') ->inertia('my-addon::DataWangjangler') ->title('Data Wangjangler') ->navTitle('Wangjangler') // Icon included in Statamic ->icon('share-mega-phone') // Custom icon ->icon('') ->description('Wanjangles your data at the click of a button.') ->docsUrl('https://yoursite.com/docs/wangjangler'); }); } ``` ## Using a custom controller Instead of passing data to the utility in your service provider, you may define a custom controller instead. ``` php Utility::register('data_wangjangler') ->action(WangjanglerController::class) // call the __invoke method ->action([WangjanglerController::class, 'index']); // call the index method ``` In your controller, you can do whatever you need to do, then return an Inertia.js Vue component: ``` php use Inertia\Inertia; class WangjanglerController { public function __invoke() { $items = Item::all(); return Inertia::render('my-addon::DataWangjangler', [ 'items' => $items, ]); } } ``` Data will be passed to the component as props: ```vue ``` ## Routing A route will be created for you automatically, using the slugified version of the handle you initially provided. eg. `/cp/utilities/data-wangjangler` If your utility needs to _do_ something (like how you click a button in the cache manager utility to actually clear the cache), you may register additional routes. ``` php Utility::register('data_wangjangler')->routes(function ($router) { $router->post('/', [WangjanglerController::class, 'process'])->name('process'); }); ``` ``` php // WangjanglerController.php public function process(Request $request) { // Do the processing... return redirect()->back()->with('success', 'Data has been wangjangled!'); } ``` You can use the `cp_route` helper in PHP to generate URLs to your utility routes: ``` php cp_route('utilities.data-wangjangler') // /cp/utilities/data-wangjangler cp_route('utilities.data-wangjangler.process') // /cp/utilities/data-wangjangler/process ``` ## Permissions A single permission will be registered automatically using the handle. eg. `access data_wangjangler utility` Users without this permission will not see the utility in the navigation or utility listing. ## Using Blade For simpler utilities, or if you prefer not to use Vue, you may build utilities using Blade, but there's a few limitations to be aware of: - Blade-rendered pages trigger a full page reload rather than the SPA-style transitions used elsewhere in the Control Panel. - Under the hood, Blade views are rendered inside a Vue component, which means ` ``` An entry can control the layout it's rendered with by setting the `layout` system variable. ``` yaml # Use /resources/views/rss.antlers.html layout: rss ``` As mentioned above, Statamic will use the `resources/views/layout.antlers.html` view as the default layout. You can change the default layout in your `config/statamic/system.php` config file: ```php 'layout' => env('STATAMIC_LAYOUT', 'layout'), ``` ## Templates Templates are views that can be used by any entry or section on your site. The template's contents will be inserted into the `{{ template_content }}` variable in your layout much like the way a painting goes into an ornate picture frame. An entry can control the template it's rendered with by setting the `template` system variable. ``` yaml # This fake entry will use /resources/views/gallery.antlers.html template: gallery title: Photo Gallery ``` :::tip You can use the [template](/fieldtypes/template) fieldtype to make choosing your template in any entry easy. Any [fieldtype](/fieldtypes) that returns a string like in the example above works too, so you have a lot of flexibility. ::: ### Map templates to entry blueprints To automatically map the template from an entry's blueprint, set the collection's default template to `@blueprint`. ``` yaml # collections/{collection}.yaml template: '@blueprint' ``` By doing this, Statamic will look for the corresponding template in `/resources/views/{collection}/{blueprint}.antlers.html`. For example, if you have an `articles` collection entry that uses a blueprint with the handle of `long`, the `/resources/views/articles/long.antlers.html` template will be used. :::tip You can still set a template on the entry level and override the default. ::: ## Partials Partials are reusable views that may find themselves in any number of other layouts, templates, and other partials. You can use any view as a partial by using the [partial](/tags/partial) tag. ``` // This will import /resources/views/blog/_card.antlers.html {{ partial:blog/card }} ``` :::best-practice We recommend prefixing any views intended to be _only_ used as partials with an underscore, `_like-this.antlers.html` and reference them `{{ partial:like-this }}`. The underscore is not necessary in the partial tag definition. ::: :::watch https://www.youtube.com/embed/Ddz6mD-jT7E We have a video about Partials too! ::: ## Using Blade If your view ends with `.blade.php` instead of `.antlers.html`, it will be rendered with Laravel's [Blade](https://laravel.com/docs/blade) engine. All of the same data will be injected into the view, but you won't have access to Statamic's [tags](/tags). This is useful if... - You want to reuse existing Laravel views and keep your markup DRY. - You have some gnarly loops to work with and can benefit from temporary variables and the `foreach` loop approach. - You're really used to using Blade and don't want to learn anything else even if it's really simple, similar, and powerful. You do you. ## Recommended conventions We recommend the following conventions for consistency. These are just suggestions, not requirements. ### Naming - Use lowercase filenames - Use hyphens to separate words - Prefix partials with _underscores - Be consistent with plurality (e.g. blog, articles, faq) ### Organizing There are a few recommended ways to organize your layouts, templates, and partials. But you don't have to take _our_ word for it. 🌈 ### Go super flat Partials are indicated by a prefixed underscore (`_header`), layout by the word `layout` and everything else is a template. **Best for small sites.** ``` files theme:serendipity-light resources/views/ _header.antlers.html about.antlers.html article.antlers.html layout.antlers.html listing.antlers.html page.antlers.html ``` ### Organize by type This is a bit more of a Statamic v2 style where views are grouped by type - partials, layouts, and templates. **Best for medium sized sites.** ``` files theme:serendipity-light resources/views/ partials/ _card.antlers.html _footer.antlers.html _nav.antlers.html layouts/ amp.antlers.html api.antlers.html main.antlers.html templates/ about.antlers.html article-list.antlers.html article-show.antlers.html faq-list.antlers.html faq-show.antlers.html form.antlers.html ``` ### Organize by section A more Laravel/application approach where views are grouped by section (or collection), along with their own partials and alternate layout files. **Best for large sites.** ``` files theme:serendipity-light resources/views/ blog/ _card.antlers.html index.antlers.html layout.antlers.html rss.antlers.html show.antlers.html contact/ index.antlers.html success.antlers.html faq/ layout.antlers.html index.antlers.html show.antlers.html layout.antlers.html ``` ================================================ FILE: content/collections/pages/vite-tooling.md ================================================ --- id: 5f26a634-19ae-4413-8b9e-1ed9c2c76bb0 blueprint: page title: 'Vite Tooling' template: page intro: 'How to use Vite in your addon.' --- ## Files We recommend using Vite to manage your addon's asset build process. To use Vite, you'll need the following files inside your addon. ``` files theme:serendipity-light your-addon/ resources/ dist/ js/ addon.js css/ addon.css src/ ServiceProvider.php vite.config.js package.json ``` ### package.json Here's `package.json`, which contains the commands you'll need to run, and the dependencies needed to run Vite. - The `laravel-vite-plugin` package allows a simpler wrapper around common Vite options, and provides hot reloading. - The `@statamic/cms` package allows you to import Vue components from Statamic. As it's not a "real" npm package, the code is being pulled from your addon's `vendor` directory. ```json { "private": true, "scripts": { "dev": "vite", "build": "vite build" }, "dependencies": { "@statamic/cms": "file:./vendor/statamic/cms/resources/dist-package" }, "devDependencies": { "laravel-vite-plugin": "^1.2.0", "vite": "^6.3.4" } } ``` :::tip Note If you aren't already, your addon should require `statamic/cms` as a Composer dependency. Otherwise, the `vendor/statamic/cms` directory won't exist. ::: ### vite.config.js Here's `vite.config.js`, which configures Vite itself. - The Laravel Vite plugin defaults to the `public` directory to place the compiled code because it's intended to be used in your app. We've changed it to `resources/dist` as we think it's a nicer convention when using in an addon. Of course, you may customize it. Whichever directory you choose, you'll need to make sure it exists. - The `statamic` plugin allows you to import Statamic's Vue components and CSS files. ```js import { defineConfig } from 'vite'; import laravel from 'laravel-vite-plugin'; import statamic from '@statamic/cms/vite-plugin'; export default defineConfig({ plugins: [ laravel({ input: [ 'resources/js/addon.js', 'resources/css/addon.css' ], publicDirectory: 'resources/dist', }), statamic(), ], }); ``` ### addon.js The `addon.js` file is responsible for registering Vue components or hooks. - Wrap any component registrations inside a `Statamic.booting()` callback to ensure components are registered _after_ Statamic has booted. - Use `Statamic.$components.register()` (or `Statamic.$inertia.register()` for Inertia.js pages) to register components. ``` js // import YourComponent from './components/YourComponent.vue'; Statamic.booting(() => { // Statamic.$components.register('component-name', YourComponent); }); ``` ### addon.css True to its name, the `addon.css` file is responsible for your addon's CSS. ```css /** Your custom styles go here */ ``` #### Tailwind CSS If you want to use [Tailwind CSS](https://tailwindcss.com) in your addon's components, you'll need to install & configure Tailwind. 1. First, install `tailwindcss` and `@tailwindcss/vite`: ```sh npm install tailwindcss @tailwindcss/vite ``` 2. Add the Tailwind Vite plugin to your `vite.config.js` file: ```js import { defineConfig } from 'vite'; import laravel from 'laravel-vite-plugin'; import statamic from '@statamic/cms/vite-plugin'; import tailwindcss from '@tailwindcss/vite'; // [tl! ++ **] export default defineConfig({ plugins: [ laravel({ input: [ 'resources/js/addon.js', 'resources/css/addon.css' ], publicDirectory: 'resources/dist', }), statamic(), tailwindcss(), // [tl! ++ **] ], }); ``` 3. In your addon's CSS file, import Statamic's `tailwind.css` file: ```css @import "@statamic/cms/tailwind.css"; ``` You don't need to `@import "tailwindcss"`, as it'll be imported by Statamic's `tailwind.css` file. ### Overriding Control Panel CSS We organize our CSS using [cascade layers](https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/Cascade_layers#issues_cascade_layers_can_solve) to help manage styling priorities and avoid conflicts. Internally, we define layers in this order: `@layer base, addon-theme, addon-utilities, components, utilities, ui, ui-states;`. #### Overriding Control Panel CSS using Tailwind If you use Tailwind, any CSS you add to your addon will automatically go into the `addon-utilities` layer. We intentionally place addon layers below Statamic’s core layers. This prevents addon styles from unintentionally overriding Statamic's core styles—for instance, an addon's `bg-something` class won’t interfere with Statamic's `dark:bg-something`, which could otherwise cause visual issues like incorrect background colors. If you do need to explicitly override Statamic’s styles, you can use the Tailwind `!` prefix (e.g., `!lg:grid-cols-3`) to force your class to take priority, which is `!important` under the hood. However, use this technique sparingly, as it will override Statamic’s intended default styling and could introduce unintended side effects. #### Overriding Control Panel CSS using plain CSS If you don't use Tailwind, you should still use the `addon-utilities` layer by default, to avoid conflicts with Statamic's core styles. If you want to override something specific in the CP CSS and your rule is clashing with a core style, there are a couple of different approaches you can take. 1. You can simply write CSS outside cascade layers, which will have higher specificity than Statamic's styles. 2. You can also use the `!important` flag inside `@layer addon-utilities { ... }` to override Statamic's styles. ### Service Provider Here's `ServiceProvider.php`, which is the PHP entry point to your addon. You should add a `$vite` property which mirrors the paths in your `vite.config.js` file. ```php class ServiceProvider extends AddonServiceProvider { protected $vite = [ // [tl! ++:start] 'input' => [ 'resources/js/addon.js', 'resources/css/addon.css', ], 'publicDirectory' => 'resources/dist', ]; // [tl! ++:end] } ``` :::tip If you use the `php please make:fieldtype` command, these files will be created automatically for you. ::: ## Development If you visit the Control Panel before running any commands, you will be greeted with a `Vite manifest not found` error. You'll need to install dependencies (the first time only) and start the development server. ```bash cd addons/your/addon npm install npm run dev ``` Now that the Vite server is running, the error in the Statamic CP should be gone once you refresh. To use Hot Module Reloading (HMR) or the [Vue Devtools](https://devtools.vuejs.org) browser extension, you need to publish a special "dev build", which can be done via the `vendor:publish` command: ```bash php artisan vendor:publish --tag=statamic-cp-dev ``` Alternatively, it can be symlinked: ```bash ln -s /path/to/vendor/statamic/cms/resources/dist-dev public/vendor/statamic/cp-dev ``` Statamic will use the dev build as long as `APP_DEBUG=true` in your `.env` and the `public/vendor/statamic/cp-dev` directory exists. You **shouldn't** commit these or use this on production. :::tip If you're using Herd or Valet with a secured site, your JS might not be loading correctly due to access control checks. You'll need Vite know about your Laravel site in `vite.config.js`. ```js export default defineConfig({ plugins: [ laravel({ detectTls: 'yoursite.test', // [tl!++] input: [ ``` ::: To avoid needing to run the development script every time you visit the Control Panel, you may wish to build your CSS & JS. ```bash npm run build ``` You may need to symlink your addon's `resources/dist` directory the first time so it points to your addon's directory: ```bash ln -s ./addons/your/addon/resources/dist public/vendor/package ``` ## Deployment When you're ready to deploy your addon, either to your own application or getting it ready to go into the marketplace, you should compile the production assets. Make sure that the Vite dev server is not running, then run: ```bash npm run build ``` The files will be compiled into `resources/dist`. If you'd like to test that everything is working you can run `php artisan vendor:publish` in your app and choose your addon's tag. The compiled assets should be copied into `public/vendor/your-addon` and they should be loaded in the Control Panel. ## Configuring Vue You may use the `Statamic.configuring()` hook to register Vue plugins and global properties before the Control Panel is mounted. ```js // addon.js Statamic.configuring(() => { Statamic.$app.use(PerfectScrollbarPlugin); Object.assign(Statamic.$app.config.globalProperties, { $something: something, }); }); ``` ================================================ FILE: content/collections/pages/vue-2-to-3.md ================================================ --- id: c3553d05-d1a8-453a-a59b-7e67dd2412a4 blueprint: page title: 'Upgrade from Vue 2 to Vue 3' intro: 'A guide for upgrading Vue 2 to 3.' template: page --- ## Overview As part of the Statamic 6 release, Vue was upgraded to version 3. ## Vite `package.json`: ```json { "devDependencies": { "@vitejs/plugin-vue2": "^6.0.1", // [tl! --] "@statamic/cms": "file:./vendor/statamic/cms/resources/dist-package" // [tl! ++] } } ``` `vite.config.js`: ```js import vue from '@vitejs/plugin-vue2'; // [tl! --] import laravel from 'laravel-vite-plugin'; import statamic from '@statamic/cms/vite-plugin'; // [tl! ++] import { defineConfig } from 'vite'; export default defineConfig({ plugins: [ statamic(), // [tl! ++] laravel({ refresh: true, input: ['resources/js/cp.js'] }), ], }); ``` ## Laravel Mix If you are still using Laravel Mix, you will need to switch to Vite. ## Composition API Converting your components to the Composition API **is not required**. However, since it is now supported, you might love it. We recommend it. It could greatly clean up your components. Check out this example component written using the Options API: ```vue ``` And now converted to the Composition API: ```vue ``` ## Imports You should be importing components and other files from `@statamic/cms`. You may have been previously importing explicit files straight from the vendor directory. This is not supported. ```js import Something from '../../../vendor/statamic/cms/resources/js/Something.vue'; // [tl! --] import { Something } from '@statamic/cms'; // [tl! ++] ``` ## Fieldtypes ### Mixins Mixins will now need to be explicitly imported. Assuming you've updated `vite.config.js` explained above, you should be able to add the following to your fieldtype: ```js import { FieldtypeMixin as Fieldtype } from '@statamic/cms'; // [tl! ++] export default { mixins: [Fieldtype], data() { return { // } } } ``` ### Events If you are manually emitting an `input` event from within a fieldtype, you should change it to `update:value`. ```js this.$emit('input', foo); // [tl! --] this.$emit('update:value', foo); // [tl! ++] ``` ::: tip You should be using `this.update()` if possible anyway. ```js this.$emit('input', foo); // [tl! --] this.update(foo); // [tl! ++] ``` ::: ## Props, events, and v-model Vue 3 changes how v-model works. ### Fieldtypes To avoid needing to change all references to the `value` prop, we've kept the prop as-is. If you are using `v-model` directly on a fieldtype component, you will need to specify `:value` now. Note that this is only if you are _using a fieldtype component_ from within another component. ```vue v-model:value="foo" /> ``` ### Components that no longer support v-model If you were using `v-model`, you must change to the appropriate prop and event: ```vue :value="foo" @input="foo = $event" /> ``` | Component | Prop | Event | |-------------------------|-----------|-------------| | `` | `to` | `@slugified` | | `` | `values` | `@updated` | ### Components that support v-model If you were *not* using `v-model` and instead using the `value` prop and `input` event, you will need to change to `model-value` and `@update:model-value`. ```vue @input="foo = $event" :model-value="foo" @update:model-value="foo = $event" /> ``` | Component | Notes | |----------------|--------------------------------------| | `` | | | `` | This is from the vue-select package. | ## Slots The slot syntax changed. If you were previously using `slot-scope`, you should change to `v-slot`: Default slot: ```html
    ``` Named slots: ```html ``` You can also use the shorthand: ```html