s_skaidocsTài liệu
Mô hình field nested-v1. Theme khai báo "field_contract": "nested-v1" trong config/theme.json. Field hệ thống giữ ở cấp gốc (item.title, term.name); field tùy chỉnh đọc qua item.fields.{key}, term.fields.{key}. Các field hệ thống như categories vẫn ở cấp gốc dù lưu dạng quan hệ. Section settings, block settings và configs không đổi. Bản release chưa chuyển đổi vẫn dùng mô hình cũ; thay đổi draft chỉ lên runtime sau khi publish.

Tra cứu tất cả field types: kiểu giá trị trả về và ví dụ sử dụng →

Các field media, gồm images và thumbs, luôn trả mảng UUID hoặc []. Dùng item.images | first | image_url. Hướng dẫn media và first.
LINGO REFERENCE · ĐÃ TRIỂN KHAI

Object entries

Truy vấn nội dung đã xuất bản bằng get, find, first, paginate, count và exists. Các tham số đặt tên không phụ thuộc thứ tự.

Tag entries/endentries đã bị loại bỏ. Dùng object entries trong layout, page và section. type là key content type được cấp cho website: dùng "posts" nếu key là posts, dùng "post" nếu key là post. Không tự chuyển số ít/số nhiều.
Lịch xuất bản post: chọn Published với giờ tương lai sẽ chuyển thành Scheduled; worker kiểm tra mỗi 30 giây theo cấu hình và chuyển sang Published khi đến hạn. Draft preview chỉ xem trước giao diện, không tự cấp quyền xem nội dung nháp. Quy tắc công khai và lịch xuất bản.

Lấy danh sách: get

LINGO
{% set posts = entries.get(type="posts", limit=8, order="published_at desc") %}
{% for post in posts %}
  <article>
    <h2><a href="/post/{{ post.slug|urlencode }}">{{ post.title }}</a></h2>
    <p>{{ post.excerpt }}</p>
  </article>
{% empty %}
  <p>Chưa có bài viết.</p>
{% endfor %}

get trả danh sách trực tiếp, không có .items. Không tìm thấy trả danh sách rỗng. Không chịu ảnh hưởng của tham số trang trong URL; dùng paginate khi cần chia trang. Link /post/… là quy ước của ví dụ; theme cần trang tương ứng.

Lấy chi tiết: find

LINGO
{% set article = entries.find(type="posts", slug=slug) %}
{% if article %}
  <h1>{{ article.title }}</h1>
  <p>{{ article.excerpt }}</p>
{% else %}
  <p>Không tìm thấy bài viết.</p>
{% endif %}

{% set article_by_id = entries.find(type="posts", id="ID-CUA-BAI-VIET") %}

Trong trang post/[slug].html, biến slug lấy từ route. Cần truyền đúng một trong hai tham số id hoặc slug. Không tìm thấy trả null; có nhiều kết quả cùng slug thì báo lỗi thay vì chọn ngẫu nhiên. find không áp dụng bộ lọc URL, nhưng vẫn kiểm tra quyền website và trạng thái đang bật is_public và đã đến giờ xuất bản. Không tự đổi HTTP status thành 404.

Đếm lượt xem: find(count_view=true)

Chỉ entries.find() hỗ trợ tham số boolean count_view, mặc định false. Tính năng đếm theo ID entry, không phụ thuộc URL hay số cấp nested route. get, first, paginate, count và exists không nhận tham số này.

LINGO · TRANG CHI TIẾT
{% set item = entries.find(type="themes", slug=slug, count_view=true) %}
{% if item %}
  <h1>{{ item.title }}</h1>
  <p>{{ item.count_view }} lượt xem</p>
{% endif %}

Trong route động, dùng biến slug hoặc lấy đoạn cuối qua request.segments|last được runtime cung cấp. Có thể truyền id thay cho slug, nhưng không truyền cả hai. item.count_view là bộ đếm hệ thống, không nằm trong item.fields; giá trị render chưa bao gồm lượt xem đang được gửi từ trang này.

  • Query chỉ đăng ký theo dõi; không tăng bộ đếm ngay khi lấy dữ liệu. Engine tự chèn script gửi POST khi trang hiển thị, developer không cần thêm script.
  • Mỗi trang theo dõi tối đa một entry chính: lần find thành công đầu tiên có count_view=true. Các lời gọi sau không tạo thêm lượt xem.
  • Chỉ runtime production được đếm. Draft, preview, trang lỗi và entry không tìm thấy không ghi nhận lượt xem.
  • Một trình duyệt được ghi nhận tối đa một lượt cho cùng entry trong 30 phút. Xóa cookie hoặc đổi trình duyệt có thể được tính là khách khác; đây không phải số người thật tuyệt đối.
  • Backend kiểm tra chữ ký, CSRF, cùng origin, đúng ứng dụng và trạng thái công khai hiện tại. Mỗi IP tối đa 60 yêu cầu/phút trên mỗi tiến trình; bot phổ biến bị bỏ qua.
  • Lỗi ghi nhận không làm hỏng trang. Trình duyệt tắt JavaScript, chặn script/cookie hoặc CSP không cho phép script sẽ không được đếm.
Triển khai backend mới và migration 179 trước khi sử dụng. Dùng APP_KEY ổn định trên tất cả instance để ký token. Nếu chưa cấu hình APP_KEY thì trang vẫn render nhưng không chèn mã ghi nhận. POST /_post/entries/view là API nội bộ của runtime, không gọi trực tiếp từ template. Token có hiệu lực 1 giờ; runtime hiện trả private, no-store, không cache HTML chứa token theo phiên.

Phân trang: paginate

LINGO
{% set listing = entries.paginate(type="posts", limit=8, page_key="posts_page") %}
{% for post in listing.items %}
  <h2>{{ post.title }}</h2>
{% empty %}
  <p>Không có kết quả.</p>
{% endfor %}
{% if listing.pagination.has_previous %}
  <a href="{{ listing.pagination.previous_url }}">Trang trước</a>
{% endif %}
{% if listing.pagination.has_next %}
  <a href="{{ listing.pagination.next_url }}">Trang tiếp</a>
{% endif %}

Đọc số trang từ URL, ví dụ ?posts_page=2. Hai danh sách độc lập cần page_key khác nhau. Kết quả gồm items, alias records và pagination.

paginationÝ nghĩa
page, per_pageTrang hiện tại từ 1, số bản ghi mỗi trang.
total, total_pagesTổng số kết quả sau lọc, số trang.
has_previous, has_nextCó trang trước/tiếp theo hay không.
previous_url, next_urlChỉ có khi có trang tương ứng. Giữ query parameter khác.

Trang nhỏ hơn 1 về 1, vượt giới hạn về trang cuối. Danh sách rỗng: page=1, total_pages=0.

first, count và exists

LINGO
{% set latest = entries.first(type="posts", order="published_at desc") %}
{% set total = entries.count(type="posts") %}
{% set has_posts = entries.exists(type="posts") %}

first trả item đầu hoặc null. count trả tổng số item sau lọc, không bị cắt theo limit. exists trả boolean. Mỗi lời gọi là một truy vấn độc lập; lưu kết quả vào biến để tái sử dụng.

Tham số

TênMặc địnhÝ nghĩa
typeBắt buộcKey content type được cấp cho website hiện tại.
limit8Số nguyên 1–100, giới hạn kết quả get hoặc kích thước trang paginate. Runtime có thể giới hạn thấp hơn.
id, slugRỗngLọc chính xác. find bắt buộc đúng một trong hai.
count_viewfalseBoolean, chỉ find. Đăng ký ghi nhận lượt xem của entry chính khi trang production hiển thị; không tăng ngay khi query.
termRỗngID của một taxonomy term, không phải key/slug; không tự mở rộng danh mục con.
qRỗngTìm không phân biệt hoa thường trên dữ liệu item serialize, không chỉ tiêu đề; chưa phải full-text search.
orderupdated_at descupdated_at, created_at, published_at, id kèm asc/desc. Không nhận SQL tùy ý.
statusTất cả trạng thái công khaiLọc thêm theo key; không vượt qua quyền công khai. Ví dụ approved chỉ trả kết quả nếu trạng thái này đang bật is_public. draft nội bộ trả rỗng.
page_keypageChỉ dành cho paginate. Bắt đầu bằng chữ hoặc _, tiếp theo chữ/số/_, tối đa 64 ký tự.

Dùng dấu phẩy giữa tham số. Sai kiểu, tham số lạ, trùng tên hoặc trộn tham số vị trí sẽ báo lỗi. Chưa hỗ trợ ids, taxonomy hoặc include_children.

Kết hợp terms

LINGO
{% set categories = terms.get(taxonomy="category", slug="technology", limit=1) %}
{% for category in categories %}
  <h2>{{ category.name }}</h2>
  {% set posts = entries.get(type="posts", term=category.id, limit=8) %}
  {% for post in posts %}
    <h3>{{ post.title }}</h3>
  {% endfor %}
{% endfor %}
LINGO
<form action="/blog" method="get" role="search">
  <label for="q">Tìm bài viết</label>
  <input id="q" name="q" type="search">
  <button type="submit">Tìm kiếm</button>
</form>
{% set listing = entries.paginate(type="posts", limit=8) %}

{% set matches = entries.get(type="posts", q="website") %}

Các lời gọi trừ find kế thừa bộ lọc request: ?q=website, các key custom field, và id, status, resource, model_key, slug, locale, post_type. Bộ lọc q trực tiếp được áp dụng thêm (AND) với bộ lọc URL, không ghi đè nó. Form bỏ tham số trang để tìm lại từ trang 1. ?term=… không tự truyền vào term của object.

Thuộc tính item

Các field chung: id, workspace_id, application_id, created_by, status, resource, model_key, content_type_id, published_at, created_at, updated_at, count_view.

Resource post có thêm title, slug, excerpt, content, locale, images, post_type, seo_title, seo_description. Model adapter chọn bản dịch theo locale runtime. Field hệ thống nằm trực tiếp trên item; field do người dùng tạo nằm trong item.fields.{key}, ví dụ item.fields.intro. Không có alias custom field ở cấp gốc. Không giả định mọi content type đều có title/slug.

Quyền truy cập và giới hạn

  • Giữ nguyên scope website/workspace, content type được cấp quyền và trạng thái đang bật is_public và đã đến giờ xuất bản. Không cho đọc draft.
  • Thiếu database/scope, type không khả dụng hoặc lỗi truy vấn sẽ báo lỗi, không giả vờ trả rỗng.
  • Hiện nạp và hydrate dữ liệu rồi lọc, chia trang trong bộ nhớ; chưa tối ưu SQL COUNT/LIMIT/OFFSET.
  • Giữ autoescape cho dữ liệu. Không dùng safe cho nội dung chưa được kiểm soát.
  • Provider tùy chỉnh trong Go hiện trả danh sách rows chưa phân trang, thay vì map pagination cũ; provider chịu trách nhiệm kiểm tra quyền và thứ tự dữ liệu.

Chuyển từ tag cũ

Thay block entries/endentries bằng set listing = entries.paginate(...); đổi entries.items thành listing.items và entries.pagination thành listing.pagination. Nếu không cần phân trang, dùng entries.get và lặp trực tiếp danh sách. Không gán biến mang tên entries.