"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 →
item.images | first | image_url. Hướng dẫn media và first.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ự.
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ấy danh sách: get
{% 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
{% 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.
{% 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.
/_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
{% 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_page | Trang hiện tại từ 1, số bản ghi mỗi trang. |
| total, total_pages | Tổng số kết quả sau lọc, số trang. |
| has_previous, has_next | Có trang trước/tiếp theo hay không. |
| previous_url, next_url | Chỉ 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
{% 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ên | Mặc định | Ý nghĩa |
|---|---|---|
| type | Bắt buộc | Key content type được cấp cho website hiện tại. |
| limit | 8 | Số 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, slug | Rỗng | Lọc chính xác. find bắt buộc đúng một trong hai. |
| count_view | false | Boolean, 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. |
| term | Rỗng | ID của một taxonomy term, không phải key/slug; không tự mở rộng danh mục con. |
| q | Rỗng | Tì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. |
| order | updated_at desc | updated_at, created_at, published_at, id kèm asc/desc. Không nhận SQL tùy ý. |
| status | Tất cả trạng thái công khai | Lọ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_key | page | Chỉ 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
{% 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 %}Tìm kiếm từ URL hoặc từ tham số
<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.