Every variable a marketplace game or block receives (contract version 2), in the preview here and on your Rafflex site. The samples are generated from the exact fixtures the preview renders with, so they never drift.
The tenant site's active competitions, pre-sorted four ways: all (the site's display order), ending_soon (soonest end first), latest (newest first), and featured (the site's featured competitions). Each entry is one competition; formatted values (price) come with _raw numeric pairs (price_raw). Take the first few with slice, for example competitions.ending_soon|slice(0, 4).
{
"all": [
{
"id": 1,
"title": "Win a MacBook Pro 16\"",
"subtitle": "Draw closes soon",
"slug": "macbook-pro-16",
"url": "#macbook-pro-16",
"image_url": "https://static.rafflex.io/marketplace/samples/image-url.png",
"featured_video_url": null,
"slider_image_url": "https://static.rafflex.io/marketplace/samples/slider-image-url.png",
"portrait_image_url": null,
"price": "£2.99",
"price_raw": 2.99,
"price_display": "£2.99",
"original_price": null,
"original_price_raw": 2.99,
"is_on_sale": false,
"is_free": false,
"has_question": true,
"question": "What is the capital of France?",
"cash_alt": "£4,200.00",
"cash_alt_raw": 4200,
"draw_date": "1 day from now",
"end_date": "1 day from now",
"draw_date_raw": "2026-10-05 16:07:23",
"end_date_raw": "2026-10-05 16:07:23",
"start_date_raw": "2026-09-19 16:07:23",
"draw_date_seconds": 172800,
"end_date_seconds": 172800,
"max_tickets": 5000,
"max_per_user": 100,
"sold_tickets": 3890,
"tickets_remaining": 1110,
"instant_wins": 25,
"instant_wins_remaining": 12,
"game_type": null,
"has_ended": false,
"is_sold_out": false,
"is_active": true,
"is_featured": true,
"simple_max_ticket": null,
"winner_qty": 1,
"auto_draw": true,
"leaderboard_enabled": false,
"percent_sold": 77.8,
"category": "Tech",
"categories": [
"Tech"
],
"description": "A sample competition so your template has data to render.",
"gallery": []
}
],
"ending_soon": [
"Same shape as competitions.all[0]"
],
"latest": [
"Same shape as competitions.all[0]"
],
"featured": [
"Same shape as competitions.all[0]"
]
}
Every category with at least one active competition, as { id, name, slug, url, competitions }, each carrying its competitions in display order.
[
{
"id": 1,
"name": "Tech",
"slug": "tech",
"url": "#category-tech",
"competitions": [
{
"id": 1,
"title": "Win a MacBook Pro 16\"",
"subtitle": "Draw closes soon",
"slug": "macbook-pro-16",
"url": "#macbook-pro-16",
"image_url": "https://static.rafflex.io/marketplace/samples/image-url.png",
"featured_video_url": null,
"slider_image_url": "https://static.rafflex.io/marketplace/samples/slider-image-url.png",
"portrait_image_url": null,
"price": "£2.99",
"price_raw": 2.99,
"price_display": "£2.99",
"original_price": null,
"original_price_raw": 2.99,
"is_on_sale": false,
"is_free": false,
"has_question": true,
"question": "What is the capital of France?",
"cash_alt": "£4,200.00",
"cash_alt_raw": 4200,
"draw_date": "1 day from now",
"end_date": "1 day from now",
"draw_date_raw": "2026-10-05 16:07:23",
"end_date_raw": "2026-10-05 16:07:23",
"start_date_raw": "2026-09-19 16:07:23",
"draw_date_seconds": 172800,
"end_date_seconds": 172800,
"max_tickets": 5000,
"max_per_user": 100,
"sold_tickets": 3890,
"tickets_remaining": 1110,
"instant_wins": 25,
"instant_wins_remaining": 12,
"game_type": null,
"has_ended": false,
"is_sold_out": false,
"is_active": true,
"is_featured": true,
"simple_max_ticket": null,
"winner_qty": 1,
"auto_draw": true,
"leaderboard_enabled": false,
"percent_sold": 77.8,
"category": "Tech",
"categories": [
"Tech"
],
"description": "A sample competition so your template has data to render.",
"gallery": []
}
]
}
]
Recent draw winners.
[
{
"name": "Sarah T.",
"prize": "MacBook Pro 16\"",
"product_title": "Win a MacBook Pro 16\"",
"product_url": "#",
"product_image": "https://static.rafflex.io/marketplace/samples/product-image.png",
"date": "Oct 1, 2026",
"date_ago": "2 days ago"
}
]
Recent instant win winners.
[
{
"winner_name": "Megan L.",
"prize": "£250 Cash",
"prize_value": "250.00",
"prize_value_raw": 250,
"is_site_credit": false,
"product_title": "£1,000 Cash Instant Win",
"product_url": "#",
"product_image": "https://static.rafflex.io/marketplace/samples/product-image.png",
"date": "Oct 2, 2026",
"date_ago": "1 day ago"
}
]
The visitor. Check user.is_logged_in before relying on personal fields.
{
"id": 1,
"name": "Alex",
"last_name": "Morgan",
"full_name": "Alex Morgan",
"display_name": "Alex M.",
"email": "[email protected]",
"is_logged_in": true,
"balance": "£12.50",
"balance_raw": 12.5
}
The site's settings: brand colours, site name, currency icon, and social links.
{
"primary_color": "#4F45E4",
"background": "#111827",
"button_text_color": "#ffffff",
"logo_url": "",
"email_logo_url": "",
"favicon_url": "",
"wallet_icon_url": "",
"site_name": "Sample Competitions",
"footer_text": "Sample Competitions Ltd.",
"currency_icon": "£",
"facebook_url": "",
"twitter_url": "",
"instagram_url": "",
"tiktok_url": "",
"youtube_url": "",
"trustpilot_score": "4.8",
"trustpilot_url": "",
"facebook_review_score": "4.9"
}
One entry per ticket in play, each { ticket_number, won, prize }. Outcomes are pre-decided by the platform, never computed at play time; prize is null on a loss. Empty outside an order context, so games must render a graceful empty state.
[
{
"ticket_number": 1042,
"won": true,
"prize": {
"name": "£1,000 Cash",
"value": "£1,000.00",
"value_raw": 1000,
"is_site_credit": false,
"image_url": "https://static.rafflex.io/marketplace/samples/image-url.png"
}
}
]
The number of entries in plays, provided because the sandbox forbids functions and caps loops.
5
How many entries in plays won.
2
Your product's media library, as a map of tag to hosted URL, such as files['background']. An entry points at one of four kinds: an image (PNG, JPG, WEBP, GIF) for an img src or CSS background, audio (MP3) for an audio src, a 3D model (binary glTF, .glb) for a loader such as three.js GLTFLoader to fetch, or an approved library build (.js) for a module import or script src. The only supported way to reference media and libraries; hardcoded external URLs are rejected at submission.
{
"background": "https://static.rafflex.io/marketplace/products/1/background.png",
"win-jingle": "https://static.rafflex.io/marketplace/products/1/win-jingle.mp3",
"prize-box": "https://static.rafflex.io/marketplace/products/1/prize-box.glb",
"three": "https://static.rafflex.io/marketplace/libraries/three/0.186.1/three-0.186.1.m..."
}
The tenant's values for the options your template declares. Reading options.heading|default('Live competitions') declares a heading field in the tenant's page builder; an unset option is simply missing, so your default applies, except toggles, which always arrive as true or false. The tenant's Categories filter is applied to competitions, categories, and products before rendering and never appears here.
{
"heading": "Ending this week",
"accent_colour": "#4F45E4",
"show_countdown": false
}
The version 1 alias of competitions.all, kept so existing templates keep rendering. New templates read competitions instead.
[
{
"id": 1,
"title": "Win a MacBook Pro 16\"",
"subtitle": "Draw closes soon",
"slug": "macbook-pro-16",
"url": "#macbook-pro-16",
"image_url": "https://static.rafflex.io/marketplace/samples/image-url.png",
"featured_video_url": null,
"slider_image_url": "https://static.rafflex.io/marketplace/samples/slider-image-url.png",
"portrait_image_url": null,
"price": "£2.99",
"price_raw": 2.99,
"price_display": "£2.99",
"original_price": null,
"original_price_raw": 2.99,
"is_on_sale": false,
"is_free": false,
"has_question": true,
"question": "What is the capital of France?",
"cash_alt": "£4,200.00",
"cash_alt_raw": 4200,
"draw_date": "1 day from now",
"end_date": "1 day from now",
"draw_date_raw": "2026-10-05 16:07:23",
"end_date_raw": "2026-10-05 16:07:23",
"start_date_raw": "2026-09-19 16:07:23",
"draw_date_seconds": 172800,
"end_date_seconds": 172800,
"max_tickets": 5000,
"max_per_user": 100,
"sold_tickets": 3890,
"tickets_remaining": 1110,
"instant_wins": 25,
"instant_wins_remaining": 12,
"game_type": null,
"has_ended": false,
"is_sold_out": false,
"is_active": true,
"is_featured": true,
"simple_max_ticket": null,
"winner_qty": 1,
"auto_draw": true,
"leaderboard_enabled": false,
"percent_sold": 77.8,
"category": "Tech",
"categories": [
"Tech"
],
"description": "A sample competition so your template has data to render.",
"gallery": []
}
]
Games do their logic in JavaScript over the injected data. The json filter turns any variable into inline JSON:
<script>
const plays = {{ plays|json }};
const settings = {{ settings|json }};
</script>
A block declares the fields a site owner can change by reading them from options with a default. Every option read becomes a field in their page builder; an option they have not set is simply missing, so your default applies in the preview and on the installed block alike.
<h2>{{ options.heading|default('Ending soon') }}</h2>
{% for competition in competitions.ending_soon|slice(0, options.limit|default(4)) %}
<a href="{{ competition.url }}" style="background: {{ options.accent_colour|default(settings.primary_color) }};">{{ competition.title }}</a>
{% endfor %}
|
Field type
|
Option name
|
|---|---|
| Text | any other name |
| Long text | body, description, intro, content, or ending _body, _description, _intro, _content |
| Colour | colour, color, or ending _colour, _color |
| Image | image, or ending _image |
| Link | url, link, or ending _url, _link |
| Toggle | starting show_, hide_, enable_, is_, has_, or ending _enabled; or a true/false default (always arrives as true or false, so a true default can be switched off) |
| Number | count, limit, columns, or ending _count, _limit, _columns, _number; or a numeric default |
| Categories | added by the platform whenever the template reads competitions, categories, or products; never read it (the site owner ticks categories and every competition list arrives already narrowed to them) |
| Repeater | any option the template loops over with {% for item in options.key %} |
The preview toolbar reseeds plays deterministically per scenario, so a state you are debugging looks identical on every reload:
Add files to your product's media library in the studio's Media panel, then reference each one by its tag. When a buyer installs your asset, the files map travels with it, so every reference resolves to its hosted URL. A files entry can point at an image, audio, a 3D model (.glb), or an approved library build.
<img src="{{ files['background'] }}" alt="">
<audio src="{{ files['win-jingle'] }}"></audio>
<script type="module">
import * as THREE from "{{ files['three'] }}";
new THREE.GLTFLoader().load("{{ files['prize-box'] }}", (gltf) => scene.add(gltf.scene));
</script>