{"result":{"depiction_url":null,"edges":{"hasreference":{"objects":[{"created":"2026-10-07T11:13:50Z","object_id":{"id":1530,"is_a":["text","documentation","reference"],"name":"doc_reference_installation_requirements","title":{"_type":"trans","tr":{"en":"Installation requirements"}},"uri":"https:\/\/zotonic.com\/id\/1530"},"seq":1},{"created":"2026-10-07T11:13:50Z","object_id":{"id":3075,"is_a":["text","documentation","adminguide"],"name":"admin_upgrade","title":{"_type":"trans","tr":{"en":"Plan and apply an upgrade"}},"uri":"https:\/\/zotonic.com\/id\/3075"},"seq":2}],"predicate":{"id":2774,"is_a":["meta","predicate"],"name":"hasreference","title":{"_type":"trans","tr":{"en":"Documentation reference"}},"uri":"https:\/\/zotonic.com\/id\/hasreference"}},"references":{"objects":[{"created":"2020-05-30T05:47:33Z","object_id":{"id":1411,"is_a":["text","documentation","developerguide"],"name":"doc_developerguide_docker","title":{"_type":"trans","tr":{"en":"Install with Docker or Podman"}},"uri":"https:\/\/zotonic.com\/id\/1411"},"seq":1000000},{"created":"2020-05-30T05:47:33Z","object_id":{"id":1752,"is_a":["text","documentation","reference","module"],"name":"doc_module_mod_acl_user_groups","title":"mod_acl_user_groups","uri":"https:\/\/zotonic.com\/id\/1752"},"seq":1000000},{"created":"2020-05-30T05:47:33Z","object_id":{"id":1481,"is_a":["text","documentation","developerguide"],"name":"doc_developerguide_deployment_env","title":{"_type":"trans","tr":{"en":"Keep development and production settings distinct"}},"uri":"https:\/\/zotonic.com\/id\/1481"},"seq":1000000},{"created":"2020-05-30T05:47:33Z","object_id":{"id":1720,"is_a":["text","documentation","reference","module"],"name":"doc_module_mod_export","title":"mod_export","uri":"https:\/\/zotonic.com\/id\/1720"},"seq":1000000},{"created":"2020-05-30T05:47:33Z","object_id":{"id":1753,"is_a":["text","documentation","reference","module"],"name":"doc_module_mod_ssl_ca","title":"mod_ssl_ca","uri":"https:\/\/zotonic.com\/id\/1753"},"seq":1000000},{"created":"2020-05-30T05:47:33Z","object_id":{"id":1754,"is_a":["text","documentation","reference","module"],"name":"doc_module_mod_ssl_letsencrypt","title":"mod_ssl_letsencrypt","uri":"https:\/\/zotonic.com\/id\/1754"},"seq":1000000},{"created":"2020-05-30T05:47:33Z","object_id":{"id":1482,"is_a":["text","documentation","reference"],"name":"doc_developerguide_configuration_port_ssl_configuration","title":"Port configurations","uri":"https:\/\/zotonic.com\/id\/1482"},"seq":1000000},{"created":"2020-05-30T05:47:33Z","object_id":{"id":1755,"is_a":["text","documentation","developerguide"],"name":"doc_developerguide_deployment_https","title":{"_type":"trans","tr":{"en":"Configure hostnames and HTTPS"}},"uri":"https:\/\/zotonic.com\/id\/1755"},"seq":1000000},{"created":"2020-05-30T05:47:33Z","object_id":{"id":1454,"is_a":["text","documentation","reference"],"name":"doc_developerguide_configuration_zotonic_configuration","title":"Global configuration","uri":"https:\/\/zotonic.com\/id\/1454"},"seq":1000000},{"created":"2020-05-30T05:47:33Z","object_id":{"id":1302,"is_a":["text","documentation","developerguide"],"name":"doc_developerguide_server_browser_interaction","title":{"_type":"trans","tr":{"en":"Handle a browser event with a wire"}},"uri":"https:\/\/zotonic.com\/id\/1302"},"seq":1000000},{"created":"2020-05-30T05:47:33Z","object_id":{"id":1301,"is_a":["text","documentation","reference","template_scomp"],"name":"doc_template_scomp_scomp_script","title":"script","uri":"https:\/\/zotonic.com\/id\/1301"},"seq":1000000},{"created":"2020-05-30T05:47:33Z","object_id":{"id":1555,"is_a":["text","documentation","reference","controller"],"name":"doc_controller_controller_file","title":"controller_file","uri":"https:\/\/zotonic.com\/id\/1555"},"seq":1000000},{"created":"2020-05-30T05:47:33Z","object_id":{"id":1556,"is_a":["text","documentation","reference","controller"],"name":"doc_controller_controller_file_id","title":"controller_file_id","uri":"https:\/\/zotonic.com\/id\/1556"},"seq":1000000},{"created":"2020-05-30T05:47:33Z","object_id":{"id":1353,"is_a":["text","documentation","developerguide"],"name":"doc_developerguide_modules","title":{"_type":"trans","tr":{"en":"Create a reusable module"}},"uri":"https:\/\/zotonic.com\/id\/1353"},"seq":1000000},{"created":"2022-02-02T20:18:50Z","object_id":{"id":1640,"is_a":["text","documentation","developerguide"],"name":"doc_developerguide_logging","title":{"_type":"trans","tr":{"en":"Write logs that explain an operation"}},"uri":"https:\/\/zotonic.com\/id\/1640"},"seq":1000000}],"predicate":{"id":332,"is_a":["meta","predicate"],"name":"references","title":{"_type":"trans","tr":{"en":"References"}},"uri":"https:\/\/zotonic.com\/id\/references"}},"refers":{"objects":[{"created":"2026-09-20T16:12:44Z","object_id":{"id":1720,"is_a":["text","documentation","reference","module"],"name":"doc_module_mod_export","title":"mod_export","uri":"https:\/\/zotonic.com\/id\/1720"},"seq":1000000},{"created":"2026-09-20T16:12:44Z","object_id":{"id":1752,"is_a":["text","documentation","reference","module"],"name":"doc_module_mod_acl_user_groups","title":"mod_acl_user_groups","uri":"https:\/\/zotonic.com\/id\/1752"},"seq":1000000},{"created":"2026-09-20T16:12:44Z","object_id":{"id":1753,"is_a":["text","documentation","reference","module"],"name":"doc_module_mod_ssl_ca","title":"mod_ssl_ca","uri":"https:\/\/zotonic.com\/id\/1753"},"seq":1000000},{"created":"2026-09-20T16:12:44Z","object_id":{"id":1754,"is_a":["text","documentation","reference","module"],"name":"doc_module_mod_ssl_letsencrypt","title":"mod_ssl_letsencrypt","uri":"https:\/\/zotonic.com\/id\/1754"},"seq":1000000}],"predicate":{"id":2409,"is_a":["meta","predicate"],"name":"refers","title":{"_type":"trans","tr":{"en":"Refers"}},"uri":"https:\/\/zotonic.com\/id\/refers"}},"relation":{"objects":[{"created":"2022-02-02T20:18:50Z","object_id":{"id":1580,"is_a":["text","documentation","cookbook"],"name":"doc_cookbook_logstash","title":{"_type":"trans","tr":{"en":"Send structured logs to Logstash"}},"uri":"https:\/\/zotonic.com\/id\/1580"},"seq":1000000}],"predicate":{"id":303,"is_a":["meta","predicate"],"name":"relation","title":{"_type":"trans","tr":{"nl":"Relatie","en":"Relation"}},"uri":"http:\/\/purl.org\/dc\/terms\/relation"}},"subject":{"objects":[{"created":"2026-09-20T16:10:29Z","object_id":{"id":2559,"is_a":["categorization","keyword","keyword_information_type"],"name":"zotonic_topic_migration_guide","title":"Migration guide","uri":"https:\/\/zotonic.com\/id\/2559"},"seq":1},{"created":"2026-09-20T16:10:33Z","object_id":{"id":2564,"is_a":["categorization","keyword","keyword_audience"],"name":"zotonic_topic_backend_developer","title":"Backend developer","uri":"https:\/\/zotonic.com\/id\/2564"},"seq":2},{"created":"2026-09-20T16:11:04Z","object_id":{"id":2658,"is_a":["categorization","keyword","keyword_task"],"name":"zotonic_topic_migrate","title":"Migrate","uri":"https:\/\/zotonic.com\/id\/2658"},"seq":3},{"created":"2026-10-07T11:13:50Z","object_id":{"id":2689,"is_a":["categorization","keyword","keyword_quality"],"name":"zotonic_topic_reliability","title":"Reliability","uri":"https:\/\/zotonic.com\/id\/2689"},"seq":4},{"created":"2026-09-20T16:10:34Z","object_id":{"id":2563,"is_a":["categorization","keyword","keyword_audience"],"name":"zotonic_topic_frontend_developer","title":"Frontend developer","uri":"https:\/\/zotonic.com\/id\/2563"},"seq":5},{"created":"2026-09-20T16:10:52Z","object_id":{"id":2691,"is_a":["categorization","keyword","keyword_quality"],"name":"zotonic_topic_maintainability","title":"Maintainability","uri":"https:\/\/zotonic.com\/id\/2691"},"seq":6}],"predicate":{"id":308,"is_a":["meta","predicate"],"name":"subject","title":{"_type":"trans","tr":{"en":"Keyword"}},"uri":"http:\/\/purl.org\/dc\/elements\/1.1\/subject"}}},"id":1455,"is_a":["text","documentation","developerguide"],"links":[{"rel":"self","target":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/1455"},{"rel":"hub","target":"https:\/\/zotonic.com\/.zotonic\/websub"}],"medium":null,"medium_url":null,"name":"doc_developerguide_upgrading","page_url":{"en":"https:\/\/zotonic.com\/docs\/1455\/upgrade-notes","x-default":"https:\/\/zotonic.com\/docs\/1455\/upgrade-notes"},"preview_url":null,"resource":{"version":36,"pivot_location_lat":null,"title":{"_type":"trans","tr":{"en":"Upgrade notes"}},"is_authoritative":true,"body":{"_type":"trans","tr":{"en":"<div class=\"section\">\n<p>These notes list the most important changes between Zotonic versions. Please read these notes carefully when upgrading to a new major Zotonic version.<\/p>\n<div class=\"section\">\n<h2>Upgrading from 0.x to 1.0<\/h2>\n<p>Before upgrading to 1.0, be sure to check the upgrade notes for the latest 0.x version, especially if you are upgrading from before 0.12. See <a class=\"reference external\" href=\"https:\/\/github.com\/zotonic\/zotonic\/blob\/0.x\/doc\/developer-guide\/upgrading.rst\">the upgrade notes on 0.x<\/a> for more information.<\/p>\n<div class=\"section\">\n<h3>Erlang\/OTP<\/h3>\n<p>This is a historical migration guide from Zotonic 0.x. For current development use Erlang\/OTP 28 and the current installation requirements; do not interpret old-version examples below as installation advice.<\/p>\n<p>Zotonic modules are now separately published as packages to <a class=\"reference external\" href=\"https:\/\/hex.pm\">Hex.pm<\/a>, which allows you to build your own Zotonic distribution and to have each of your sites depend on the Zotonic modules (and other Erlang packages) it needs.<\/p>\n<p>This was done by restructuring Zotonic into an <a class=\"reference external\" href=\"https:\/\/www.rebar3.org\/v3\/docs\/from-rebar-2x-to-rebar3#section-required-directory-structure\">umbrella application<\/a>. The <code class=\"docutils literal notranslate\"><span class=\"pre\">src\/<\/span><\/code> directory was moved to new <code class=\"docutils literal notranslate\"><span class=\"pre\">zotonic_core<\/span><\/code> app.<\/p>\n<p>Before:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"p\">-<\/span><span class=\"ni\">include_lib<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;zotonic.hrl&quot;<\/span><span class=\"p\">).<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>After:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"p\">-<\/span><span class=\"ni\">include_lib<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;zotonic_core\/include\/zotonic.hrl&quot;<\/span><span class=\"p\">).<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>The HTTP and SMTP listeners were moved to a new <code class=\"docutils literal notranslate\"><span class=\"pre\">zotonic_listen_http<\/span><\/code> and <code class=\"docutils literal notranslate\"><span class=\"pre\">zotonic_listen_smtp<\/span><\/code> app respectively.<\/p>\n<p>A <code class=\"docutils literal notranslate\"><span class=\"pre\">zotonic_launcher<\/span><\/code> app was introduced for starting Zotonic.<\/p>\n<p>All built-in modules, the testsandbox and the status sites are now to be found in the <code class=\"docutils literal notranslate\"><span class=\"pre\">apps\/<\/span><\/code> directory.<\/p>\n<\/div>\n<div class=\"section\">\n<h3>Docker<\/h3>\n<p>Only the <a class=\"reference external\" href=\"https:\/\/hub.docker.com\/r\/zotonic\/zotonic-dev\/\">zotonic\/zotonic-dev<\/a> image is now available and automatically updated.<\/p>\n<p>We have decided to drop the other Docker images as in practice everybody was creating their own production images anyway.<\/p>\n<p>All files used by the Docker container are now placed in the <code class=\"file docutils literal notranslate\"><span class=\"pre\">docker-data<\/span><\/code> directory.<\/p>\n<p>See for more information <a class=\"reference internal\" href=\"\/id\/1411#guide-docker\"><span class=\"std std-ref\">Docker<\/span><\/a>.<\/p>\n<\/div>\n<div class=\"section\">\n<h3>Sites and modules are now OTP apps<\/h3>\n<p>Both sites and modules now follow the standard <a class=\"reference external\" href=\"http:\/\/erlang.org\/doc\/design_principles\/applications.html#id82228\">OTP directory structure<\/a>, which means all Erlang files should reside in <code class=\"file docutils literal notranslate\"><span class=\"pre\">src\/<\/span><\/code> and all other files (templates, dispatch rules etc.) in <code class=\"file docutils literal notranslate\"><span class=\"pre\">priv<\/span><\/code>\/.<\/p>\n<p>Before:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"n\">yoursite<\/span><span class=\"o\">\/<\/span>\n    <span class=\"n\">models<\/span><span class=\"o\">\/<\/span>\n        <span class=\"n\">m_some_model<\/span><span class=\"p\">.<\/span><span class=\"n\">erl<\/span>\n    <span class=\"n\">templates<\/span><span class=\"o\">\/<\/span>\n        <span class=\"n\">some_template<\/span><span class=\"p\">.<\/span><span class=\"n\">tpl<\/span>\n    <span class=\"n\">yoursite<\/span><span class=\"p\">.<\/span><span class=\"n\">erl<\/span>\n    <span class=\"n\">config<\/span>\n    <span class=\"p\">...<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>After:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"n\">yoursite<\/span><span class=\"o\">\/<\/span>\n    <span class=\"n\">priv<\/span><span class=\"o\">\/<\/span>\n        <span class=\"n\">zotonic_site<\/span><span class=\"p\">.<\/span><span class=\"n\">config<\/span>\n        <span class=\"n\">templates<\/span><span class=\"o\">\/<\/span><span class=\"n\">some_template<\/span><span class=\"p\">.<\/span><span class=\"n\">tpl<\/span>\n        <span class=\"p\">...<\/span>\n    <span class=\"n\">src<\/span><span class=\"o\">\/<\/span>\n        <span class=\"n\">models<\/span><span class=\"o\">\/<\/span><span class=\"n\">m_some_model<\/span><span class=\"p\">.<\/span><span class=\"n\">erl<\/span>\n        <span class=\"n\">yoursite<\/span><span class=\"p\">.<\/span><span class=\"n\">erl<\/span>\n        <span class=\"n\">yoursite<\/span><span class=\"p\">.<\/span><span class=\"n\">app<\/span><span class=\"p\">.<\/span><span class=\"n\">src<\/span>\n        <span class=\"p\">...<\/span>\n    <span class=\"n\">rebar<\/span><span class=\"p\">.<\/span><span class=\"n\">config<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>The <code class=\"docutils literal notranslate\"><span class=\"pre\">user_sites_dir<\/span><\/code> and <code class=\"docutils literal notranslate\"><span class=\"pre\">user_modules_dir<\/span><\/code> configurations have been removed. The default location for sites and modules is now the <code class=\"docutils literal notranslate\"><span class=\"pre\">apps_user<\/span><\/code> directory. With the <code class=\"docutils literal notranslate\"><span class=\"pre\">ZOTONIC_APPS<\/span><\/code> environment variable you can define an additional source directory outside the Zotonic umbrella <code class=\"docutils literal notranslate\"><span class=\"pre\">apps<\/span><\/code> directory.<\/p>\n<p>To upgrade, move your <code class=\"docutils literal notranslate\"><span class=\"pre\">user\/modules<\/span><\/code> and <code class=\"docutils literal notranslate\"><span class=\"pre\">user\/sites<\/span><\/code> applications to the <code class=\"docutils literal notranslate\"><span class=\"pre\">apps_user<\/span><\/code> directory.<\/p>\n<p>Also ensure that module directories are renamed, for example <code>mod_mymod<\/code> becomes:<\/p>\n<pre>zotonic_mod_mymod<br \/>    src\/mod_mymod.erl<br \/>    src\/zotonic_mod_mymod.app.src<\/pre>\n<\/div>\n<div class=\"section\">\n<p>Sites do not need to be renamed, though they <em>must<\/em> have the <code>priv\/zotonic_site.config<\/code> file.<\/p>\n<h3>Resources<\/h3>\n<p>All resources are now <em>maps<\/em> with <em>binary<\/em> keys. Use of the previous 0.x proplists is deprecated, the fetch routines will always return maps, the update routines will convert property lists to maps before updating.<\/p>\n<p>The <code class=\"docutils literal notranslate\"><span class=\"pre\">name_to_id_check\/2<\/span><\/code> functions were removed from <code class=\"docutils literal notranslate\"><span class=\"pre\">m_category<\/span><\/code>, <code class=\"docutils literal notranslate\"><span class=\"pre\">m_predicate<\/span><\/code> and <code class=\"docutils literal notranslate\"><span class=\"pre\">m_rsc<\/span><\/code>.<\/p>\n<p>Before:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"nv\">Id<\/span> <span class=\"o\">=<\/span> <span class=\"nn\">m_rsc<\/span><span class=\"p\">:<\/span><span class=\"nf\">name_to_id_check<\/span><span class=\"p\">(<\/span><span class=\"nv\">Value<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Context<\/span><span class=\"p\">).<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>After:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"p\">{<\/span><span class=\"n\">ok<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Id<\/span><span class=\"p\">}<\/span> <span class=\"o\">=<\/span> <span class=\"nn\">m_rsc<\/span><span class=\"p\">:<\/span><span class=\"nf\">name_to_id<\/span><span class=\"p\">(<\/span><span class=\"nv\">Value<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Context<\/span><span class=\"p\">).<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>Inserting or deleting an edge no longer modifies the last modified and modifier properties of the edge’s subject resource.<\/p>\n<p>There are extra access controls on rsc properties. The <code class=\"docutils literal notranslate\"><span class=\"pre\">privacy<\/span><\/code> property controls what is visible for whom.<\/p>\n<p>The function <code class=\"docutils literal notranslate\"><span class=\"pre\">m_rsc:get_visible\/2<\/span><\/code> has been removed. The function <code class=\"docutils literal notranslate\"><span class=\"pre\">m_rsc:get\/2<\/span><\/code> now checks on visibility of properties. To fetch all properties, either use <code class=\"docutils literal notranslate\"><span class=\"pre\">m_rsc:get_raw\/2<\/span><\/code> or call <code class=\"docutils literal notranslate\"><span class=\"pre\">m_rsc:get\/2<\/span><\/code> as a administrator level user.<\/p>\n<\/div>\n<div class=\"section\">\n<h3>Media<\/h3>\n<p>The medium record is now a <em>map<\/em> with <em>binary<\/em> keys. Use of the previous 0.x proplists is deprecated, the fetch routines will always return maps, the update routines will convert property lists to maps before updating.<\/p>\n<\/div>\n<div class=\"section\">\n<h3>ACL<\/h3>\n<p>mod_acl_adminonly was replaced by <a class=\"reference internal\" href=\"\/id\/1752\"><span class=\"std std-ref\">mod_acl_user_groups<\/span><\/a>. To create users that have access to the admin, add them to the ‘Managers’ user group.<\/p>\n<p>The <code class=\"docutils literal notranslate\"><span class=\"pre\">visible_for<\/span><\/code> property semantics and the the <code class=\"docutils literal notranslate\"><span class=\"pre\">acl_can_see<\/span><\/code> notification were removed. You can get similar functionality by adding users to user and collaboration groups. These are provided by mod_acl_user_groups. The <code class=\"docutils literal notranslate\"><span class=\"pre\">visible_for<\/span><\/code> <code class=\"docutils literal notranslate\"><span class=\"pre\">rsc<\/span><\/code> table property has been kept for BC. So if you’re using mod_acl_adminonly, mod_acl_simple_roles or a custom ACL module you can still rely on the property.<\/p>\n<p>The <code class=\"docutils literal notranslate\"><span class=\"pre\">acl_rsc_update_check<\/span><\/code> notification was removed.<\/p>\n<\/div>\n<div class=\"section\">\n<h3>Authentication<\/h3>\n<p>All auth notifications values were converted to records.<\/p>\n<p>Before:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"nf\">observe_auth_logon<\/span><span class=\"p\">(<\/span><span class=\"n\">auth_logon<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Context<\/span><span class=\"p\">,<\/span> <span class=\"p\">_<\/span><span class=\"nv\">Context<\/span><span class=\"p\">)<\/span> <span class=\"o\">-&gt;<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>After:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"nf\">observe_auth_logon<\/span><span class=\"p\">(<\/span><span class=\"nl\">#auth_logon<\/span><span class=\"p\">{},<\/span> <span class=\"nv\">Context<\/span><span class=\"p\">,<\/span> <span class=\"p\">_<\/span><span class=\"nv\">Context<\/span><span class=\"p\">)<\/span> <span class=\"o\">-&gt;<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"section\">\n<h3>Configuration<\/h3>\n<p>Port configuration <a class=\"reference internal\" href=\"\/id\/1481#guide-deployment-env\"><span class=\"std std-ref\">environment variables<\/span><\/a> were changed.<\/p>\n<p>Before:<\/p>\n<div class=\"highlight-bash notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"nv\">ZOTONIC_PORT<\/span><span class=\"o\">=<\/span><span class=\"m\">80<\/span> <span class=\"nv\">ZOTONIC_SSL_PORT<\/span><span class=\"o\">=<\/span><span class=\"m\">443<\/span> bin\/zotonic start\n<\/pre>\n<\/div>\n<\/div>\n<p>After:<\/p>\n<div class=\"highlight-bash notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"nv\">ZOTONIC_LISTEN_PORT<\/span><span class=\"o\">=<\/span><span class=\"m\">80<\/span> <span class=\"nv\">ZOTONIC_SSL_LISTEN_PORT<\/span><span class=\"o\">=<\/span><span class=\"m\">443<\/span> bin\/zotonic start\n<\/pre>\n<\/div>\n<\/div>\n<p>Black\/white-lists are now called block\/allow-lists.<\/p>\n<ul class=\"simple\">\n<li><code class=\"docutils literal notranslate\"><span class=\"pre\">proxy_whitelist<\/span><\/code> is now <code class=\"docutils literal notranslate\"><span class=\"pre\">proxy_allowlist<\/span><\/code><\/li>\n<li><code class=\"docutils literal notranslate\"><span class=\"pre\">smtp_dnsbl<\/span><\/code> is now <code class=\"docutils literal notranslate\"><span class=\"pre\">smtp_dns_blocklist<\/span><\/code><\/li>\n<li><code class=\"docutils literal notranslate\"><span class=\"pre\">smtp_dnswl<\/span><\/code> is now <code class=\"docutils literal notranslate\"><span class=\"pre\">smtp_dns_allowlist<\/span><\/code><\/li>\n<li><code class=\"docutils literal notranslate\"><span class=\"pre\">ip_whitelist<\/span><\/code> is now <code class=\"docutils literal notranslate\"><span class=\"pre\">ip_allowlist<\/span><\/code><\/li>\n<li><code class=\"docutils literal notranslate\"><span class=\"pre\">ip_whitelist_system_management<\/span><\/code> is now <code class=\"docutils literal notranslate\"><span class=\"pre\">ip_allowlist_system_management<\/span><\/code><\/li>\n<\/ul>\n<p>If an IP is on DNS allowlist then <code class=\"docutils literal notranslate\"><span class=\"pre\">z_email_dnsbl:status\/2<\/span><\/code> returns now <code class=\"docutils literal notranslate\"><span class=\"pre\">{ok,<\/span> <span class=\"pre\">allowed}<\/span><\/code>.<\/p>\n<\/div>\n<div class=\"section\">\n<h3>Errors<\/h3>\n<p><code class=\"docutils literal notranslate\"><span class=\"pre\">m_edge<\/span><\/code>, <code class=\"docutils literal notranslate\"><span class=\"pre\">m_identity<\/span><\/code>, <code class=\"docutils literal notranslate\"><span class=\"pre\">m_rsc<\/span><\/code>, <code class=\"docutils literal notranslate\"><span class=\"pre\">m_rsc_import<\/span><\/code> and <code class=\"docutils literal notranslate\"><span class=\"pre\">m_rsc_update<\/span><\/code> no longer throw exceptions. Instead, they return an <code class=\"docutils literal notranslate\"><span class=\"pre\">{error,<\/span> <span class=\"pre\">atom()}<\/span><\/code> tuple on failure.<\/p>\n<p>Before:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"nn\">m_edge<\/span><span class=\"p\">:<\/span><span class=\"nf\">insert<\/span><span class=\"p\">(<\/span><span class=\"nv\">Id<\/span><span class=\"p\">,<\/span> <span class=\"n\">this_predicate_does_not_exist<\/span><span class=\"p\">,<\/span> <span class=\"nv\">UserId<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Context<\/span><span class=\"p\">).<\/span>\n<span class=\"c\">%% crashes with an exception<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>After:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"nn\">m_edge<\/span><span class=\"p\">:<\/span><span class=\"nf\">insert<\/span><span class=\"p\">(<\/span><span class=\"nv\">Id<\/span><span class=\"p\">,<\/span> <span class=\"n\">this_predicate_does_not_exist<\/span><span class=\"p\">,<\/span> <span class=\"nv\">UserId<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Context<\/span><span class=\"p\">).<\/span>\n<span class=\"c\">%% fails silently, so to make it crash:<\/span>\n\n<span class=\"p\">{<\/span><span class=\"n\">ok<\/span><span class=\"p\">,<\/span> <span class=\"p\">_<\/span><span class=\"nv\">EdgeId<\/span><span class=\"p\">}<\/span> <span class=\"o\">=<\/span> <span class=\"nn\">m_edge<\/span><span class=\"p\">:<\/span><span class=\"nf\">insert<\/span><span class=\"p\">(<\/span><span class=\"nv\">Id<\/span><span class=\"p\">,<\/span> <span class=\"n\">this_predicate_does_not_exist<\/span><span class=\"p\">,<\/span> <span class=\"nv\">UserId<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Context<\/span><span class=\"p\">).<\/span>\n\n<span class=\"c\">%% alternatively:<\/span>\n<span class=\"k\">case<\/span> <span class=\"nn\">m_edge<\/span><span class=\"p\">:<\/span><span class=\"nf\">insert<\/span><span class=\"p\">(<\/span><span class=\"nv\">Id<\/span><span class=\"p\">,<\/span> <span class=\"n\">this_predicate_does_not_exist<\/span><span class=\"p\">,<\/span> <span class=\"nv\">UserId<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Context<\/span><span class=\"p\">)<\/span> <span class=\"k\">of<\/span>\n    <span class=\"p\">{<\/span><span class=\"n\">ok<\/span><span class=\"p\">,<\/span> <span class=\"p\">_<\/span><span class=\"nv\">EdgeId<\/span><span class=\"p\">}<\/span> <span class=\"o\">-&gt;<\/span>\n        <span class=\"s\">&quot;Everything fine!&quot;<\/span><span class=\"p\">;<\/span>\n    <span class=\"p\">{<\/span><span class=\"n\">error<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Reason<\/span><span class=\"p\">}<\/span> <span class=\"o\">-&gt;<\/span>\n        <span class=\"s\">&quot;Something went wrong!&quot;<\/span>\n<span class=\"k\">end<\/span><span class=\"p\">.<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"section\">\n<h3>Logging<\/h3>\n<aside class=\"admonition seealso\">\n<p class=\"first admonition-title\">See also<\/p>\n<p class=\"last\"><a class=\"reference internal\" href=\"\/id\/1580#cookbook-logstash\"><span class=\"std std-ref\">Logging to Logstash<\/span><\/a><\/p>\n<\/aside>\n<p>The <code class=\"docutils literal notranslate\"><span class=\"pre\">lager<\/span><\/code> logger has been removed and replaced with the standard <a class=\"reference external\" href=\"https:\/\/www.erlang.org\/doc\/apps\/kernel\/logger_chapter.html\">erlang logger application<\/a>.<\/p>\n<p>For this to work:<\/p>\n<ul class=\"simple\">\n<li>Add the logger configuration to the <code class=\"docutils literal notranslate\"><span class=\"pre\">erlang.config<\/span><\/code> file. See <a class=\"reference internal\" href=\"\/id\/1640#dev-logging\"><span class=\"std std-ref\">Logging<\/span><\/a> for an example.<\/li>\n<li>Remove the <code class=\"docutils literal notranslate\"><span class=\"pre\">lager<\/span><\/code> definition from the erlang.config file.<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\">\n<h3>Export<\/h3>\n<p>Modules mod_atom and mod_atom_feed were removed. You can export data in a variety of formats using <a class=\"reference internal\" href=\"\/id\/1720\"><span class=\"std std-ref\">mod_export<\/span><\/a>.<\/p>\n<\/div>\n<div class=\"section\">\n<h3>JSON<\/h3>\n<p>Mochijson structures replaced with Erlang maps.<\/p>\n<p>Use the Zotonic JSON wrapper through <code class=\"docutils literal notranslate\"><span class=\"pre\">z_json:encode\/1<\/span><\/code> and <code class=\"docutils literal notranslate\"><span class=\"pre\">z_json:decode\/1<\/span><\/code>.<\/p>\n<p><code class=\"docutils literal notranslate\"><span class=\"pre\">{trans,<\/span> <span class=\"pre\">_}<\/span><\/code> tuples should now be unpacked by the client, before calling <code class=\"docutils literal notranslate\"><span class=\"pre\">z_json:encode\/1<\/span><\/code> (previously <code class=\"docutils literal notranslate\"><span class=\"pre\">z_json:to_mochijson\/2<\/span><\/code>).<\/p>\n<\/div>\n<div class=\"section\">\n<h3>Removed or deprecated functions<\/h3>\n<p>Deprecated functions have been removed from <code class=\"docutils literal notranslate\"><span class=\"pre\">z_utils<\/span><\/code>. Use the <code class=\"docutils literal notranslate\"><span class=\"pre\">z_url<\/span><\/code> and <code class=\"docutils literal notranslate\"><span class=\"pre\">z_json<\/span><\/code> modules instead.<\/p>\n<p>Deprecated function <code class=\"docutils literal notranslate\"><span class=\"pre\">z_utils:name_for_host\/2<\/span><\/code> has been removed; use <code class=\"docutils literal notranslate\"><span class=\"pre\">z_utils:name_for_site\/2<\/span><\/code> instead.<\/p>\n<p>The <code class=\"docutils literal notranslate\"><span class=\"pre\">{%<\/span> <span class=\"pre\">stream<\/span> <span class=\"pre\">%}<\/span><\/code> tag was removed, use MQTT websocket instead<\/p>\n<p>Removed older TinyMCE versions 3.5.0 and 4.2.4.<\/p>\n<p><code class=\"docutils literal notranslate\"><span class=\"pre\">z_utils:combine\/2<\/span><\/code> is removed, use <code class=\"docutils literal notranslate\"><span class=\"pre\">lists:join\/2<\/span><\/code> instead.<\/p>\n<p><code class=\"docutils literal notranslate\"><span class=\"pre\">z_utils:combine_defined\/2<\/span><\/code> is renamed to <code class=\"docutils literal notranslate\"><span class=\"pre\">z_utils:join_defined\/2<\/span><\/code>.<\/p>\n<\/div>\n<div class=\"section\">\n<h3>Module schema and data initialization<\/h3>\n<p>The <cite>#datamodel.data<\/cite> field has been removed. The notifier <cite>#manage_data<\/cite> has also been removed.<\/p>\n<p>Now the call to (the optional) <cite>manage_schema\/2<\/cite> will be followed by a call to <cite>manage_data\/2<\/cite>. Note that <cite>manage_data<\/cite> will be called if and only if you have a <cite>manage_schema\/2<\/cite> function exported (and the <cite>-mod_schema(..)<\/cite> version changes or the module is installed).<\/p>\n<p>The <cite>manage_schema\/2<\/cite> function is called inside a transaction. The <cite>manage_data\/2<\/cite> function is called after that transaction and also after all (optional) <cite>#datamodel<\/cite> changes are applied.<\/p>\n<\/div>\n<div class=\"section\">\n<h3>Templates<\/h3>\n<p>The <code class=\"docutils literal notranslate\"><span class=\"pre\">use_absolute_url<\/span><\/code> argument of the <code class=\"docutils literal notranslate\"><span class=\"pre\">url<\/span><\/code>, <code class=\"docutils literal notranslate\"><span class=\"pre\">image<\/span><\/code> and <code class=\"docutils literal notranslate\"><span class=\"pre\">lib<\/span><\/code> tags was renamed to <code class=\"docutils literal notranslate\"><span class=\"pre\">absolute_url<\/span><\/code>.<\/p>\n<p>Templates are now stored in <code class=\"file docutils literal notranslate\"><span class=\"pre\">yoursite\/priv\/templates\/<\/span><\/code> instead of <code class=\"file docutils literal notranslate\"><span class=\"pre\">yoursite\/templates\/<\/span><\/code>.<\/p>\n<p>The <code class=\"docutils literal notranslate\"><span class=\"pre\">maxage<\/span><\/code> caching argument was renamed to <code class=\"docutils literal notranslate\"><span class=\"pre\">max_age<\/span><\/code>.<\/p>\n<p>The models have now extra ACL checks.<\/p>\n<p>The <code class=\"docutils literal notranslate\"><span class=\"pre\">m.config<\/span><\/code>, <code class=\"docutils literal notranslate\"><span class=\"pre\">m.site<\/span><\/code> and <code class=\"docutils literal notranslate\"><span class=\"pre\">m.sysconfig<\/span><\/code> models are only accessible as administrator. Use the models <em>owning<\/em> the various settings to access the configurations.<\/p>\n<p>Exception is that the hostname and site-title information is publicly accessible using <code class=\"docutils literal notranslate\"><span class=\"pre\">m.site<\/span><\/code>.<\/p>\n<p>Examples:<\/p>\n<ul class=\"simple\">\n<li><code class=\"docutils literal notranslate\"><span class=\"pre\">m.config.site.title.value<\/span><\/code> is now <code class=\"docutils literal notranslate\"><span class=\"pre\">m.site.title<\/span><\/code><\/li>\n<li><code class=\"docutils literal notranslate\"><span class=\"pre\">m.config.mod_editor_tinymce.version.value<\/span><\/code> is now <code class=\"docutils literal notranslate\"><span class=\"pre\">m.editor_tinymce.version<\/span><\/code><\/li>\n<\/ul>\n<p>Check the various models of the modules for the new lookups.<\/p>\n<p>The <code class=\"docutils literal notranslate\"><span class=\"pre\">catinclude<\/span><\/code> for a resource with an unique name now looks for (assuming the unique name is <code class=\"docutils literal notranslate\"><span class=\"pre\">my_unique_name<\/span><\/code> and the template is <code class=\"docutils literal notranslate\"><span class=\"pre\">page.tpl<\/span><\/code>): <code class=\"docutils literal notranslate\"><span class=\"pre\">page.name.my_unique_name.tpl<\/span><\/code> and <strong>not<\/strong> anymore for <code class=\"docutils literal notranslate\"><span class=\"pre\">page.my_unique_name.tpl<\/span><\/code>. Rename your templates accordingly.<\/p>\n<p>The category property <code class=\"docutils literal notranslate\"><span class=\"pre\">feature_show_address<\/span><\/code> property is now called <code class=\"docutils literal notranslate\"><span class=\"pre\">is_feature_show_address<\/span><\/code>. All feature properties should be called <code class=\"docutils literal notranslate\"><span class=\"pre\">is_feature_...<\/span><\/code> to obtain a proper boolean value after the category edit form is saved.<\/p>\n<\/div>\n<div class=\"section\">\n<h3>Port, proxies and SSL certificates<\/h3>\n<p>SSL\/https support has been completely refactored.<\/p>\n<ul class=\"simple\">\n<li>SSL self signed certificates have been moved into the core<\/li>\n<li>New modules <a class=\"reference internal\" href=\"\/id\/1753\"><span class=\"std std-ref\">mod_ssl_ca<\/span><\/a> and <a class=\"reference internal\" href=\"\/id\/1754\"><span class=\"std std-ref\">mod_ssl_letsencrypt<\/span><\/a><\/li>\n<li>Deleted module <code class=\"docutils literal notranslate\"><span class=\"pre\">mod_ssl<\/span><\/code><\/li>\n<li>Port configuration has been changed, see <a class=\"reference internal\" href=\"\/id\/1482#ref-port-ssl-configuration\"><span class=\"std std-ref\">Port configurations<\/span><\/a><\/li>\n<li>If you have a <code class=\"docutils literal notranslate\"><span class=\"pre\">priv\/ssl<\/span><\/code> directory in your site, rename it to <code class=\"docutils literal notranslate\"><span class=\"pre\">priv\/security<\/span><\/code><\/li>\n<\/ul>\n<p>For an overview of https support, see <a class=\"reference internal\" href=\"\/id\/1755#https-support\"><span class=\"std std-ref\">HTTPS support<\/span><\/a><\/p>\n<\/div>\n<div class=\"section\">\n<h3>Erlang code, Controllers, Event handlers<\/h3>\n<p>If you made a site using custom controllers or request handling then you need to adapt your Erlang code. Zotonic is now using Cowboy under the hood for the http handling, previously this was MochiWeb.<\/p>\n<p>The following changes are made:<\/p>\n<ul class=\"simple\">\n<li>Binaries for all request variables and arguments.<\/li>\n<li>Events use binaries for strings in templates.<\/li>\n<li>Cookies are binaries.<\/li>\n<li>Request headers are binaries.<\/li>\n<li>Controllers initialization callbacks are removed.<\/li>\n<li>Controller callbacks have a single <em>Context<\/em> argument.<\/li>\n<li>Custom websocket handlers are removed, implement your own using Cowboy.<\/li>\n<li>The include file <code class=\"docutils literal notranslate\"><span class=\"pre\">include\/controller_webmachine_helper.hrl<\/span><\/code> is removed (and not needed anymore).<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\">\n<h3>Binaries for request variables<\/h3>\n<p>If you request an argument with <code class=\"docutils literal notranslate\"><span class=\"pre\">z_context:get_q\/2<\/span><\/code> and related functions then you might need to adapt some code. Requesting a query argument using an <em>atom<\/em> or <em>binary<\/em> will return a <em>binary<\/em>. Requesting with a <em>string<\/em> returns a string, this is for backwards compatibility. The function <code class=\"docutils literal notranslate\"><span class=\"pre\">get_q_all<\/span><\/code> will return all arguments as binaries.<\/p>\n<p>In short:<\/p>\n<ul class=\"simple\">\n<li><code class=\"docutils literal notranslate\"><span class=\"pre\">z_context:get_q(&lt;&lt;&quot;arg&quot;&gt;&gt;,<\/span> <span class=\"pre\">Context)<\/span><\/code> returns <code class=\"docutils literal notranslate\"><span class=\"pre\">&lt;&lt;&quot;value&quot;&gt;&gt;<\/span><\/code><\/li>\n<li><code class=\"docutils literal notranslate\"><span class=\"pre\">z_context:get_q(arg,<\/span> <span class=\"pre\">Context)<\/span><\/code> returns <code class=\"docutils literal notranslate\"><span class=\"pre\">&lt;&lt;&quot;value&quot;&gt;&gt;<\/span><\/code><\/li>\n<li><code class=\"docutils literal notranslate\"><span class=\"pre\">z_context:get_q(&quot;arg&quot;,<\/span> <span class=\"pre\">Context)<\/span><\/code> returns <code class=\"docutils literal notranslate\"><span class=\"pre\">&quot;value&quot;<\/span><\/code><\/li>\n<li><code class=\"docutils literal notranslate\"><span class=\"pre\">z_context:get_q_all(Context)<\/span><\/code> returns <code class=\"docutils literal notranslate\"><span class=\"pre\">[<\/span> <span class=\"pre\">{&lt;&lt;&quot;arg&quot;&gt;&gt;,&lt;&lt;&quot;value&quot;&gt;&gt;},<\/span> <span class=\"pre\">...]<\/span><\/code><\/li>\n<\/ul>\n<p>The binary name is the preferred way to request arguments.<\/p>\n<\/div>\n<div class=\"section\">\n<h3>Events like submit, postback and postback_notify<\/h3>\n<p>Strings in the <code class=\"docutils literal notranslate\"><span class=\"pre\">#submit{}<\/span><\/code>, <code class=\"docutils literal notranslate\"><span class=\"pre\">#postback{}<\/span><\/code> and <code class=\"docutils literal notranslate\"><span class=\"pre\">#postback_notify{}<\/span><\/code> events are now binaries. This is especially the case for the message, trigger, target, and form fields.<\/p>\n<p>For example, replace <code class=\"docutils literal notranslate\"><span class=\"pre\">#submit{message=&quot;hello&quot;}<\/span><\/code> with <code class=\"docutils literal notranslate\"><span class=\"pre\">#submit{message<\/span> <span class=\"pre\">=<\/span> <span class=\"pre\">&lt;&lt;&quot;hello&quot;&gt;&gt;}<\/span><\/code>. Watch the space between <code class=\"docutils literal notranslate\"><span class=\"pre\">=<\/span><\/code> and the <code class=\"docutils literal notranslate\"><span class=\"pre\">&lt;&lt;&quot;...&quot;&gt;&gt;<\/span><\/code>, without the space you will get a syntax error.<\/p>\n<\/div>\n<div class=\"section\">\n<h3>Cookies<\/h3>\n<p>Use binaries for fetching and setting cookie names and values, don’t use strings.<\/p>\n<\/div>\n<div class=\"section\">\n<h3>Request and response headers<\/h3>\n<p>All request and response headers now use binary names and values, do not use strings.<\/p>\n<p>The request and response header names are normalized to lowercase names, so always use <code class=\"docutils literal notranslate\"><span class=\"pre\">&lt;&lt;&quot;x-my-header&quot;&gt;&gt;<\/span><\/code> and <em>never<\/em> <code class=\"docutils literal notranslate\"><span class=\"pre\">&lt;&lt;&quot;X-My-Header&quot;&gt;&gt;<\/span><\/code>.<\/p>\n<p>The header values are passed as-is, and they are always binaries.<\/p>\n<\/div>\n<div class=\"section\">\n<h3>Controllers<\/h3>\n<p>The controllers are simplified and will need some adaptations.<\/p>\n<p>The following callbacks are removed:<\/p>\n<ul class=\"simple\">\n<li><code class=\"docutils literal notranslate\"><span class=\"pre\">init<\/span><\/code><\/li>\n<li><code class=\"docutils literal notranslate\"><span class=\"pre\">ping<\/span><\/code><\/li>\n<\/ul>\n<p>Most resource callbacks now have a single <em>Context<\/em> argument; <code>process\/4<\/code> receives method, accepted\/provided content types and context. the <em>ReqData<\/em> argument has been removed. There is no need anymore for the <code class=\"docutils literal notranslate\"><span class=\"pre\">?WM_REQ<\/span><\/code> and <code class=\"docutils literal notranslate\"><span class=\"pre\">?WM_REPLY<\/span><\/code> macros, and they have been removed.<\/p>\n<p>Other controller changes changes are:<\/p>\n<ul class=\"simple\">\n<li>Content types are now binaries in <cite>content_types_accepted<\/cite> and <cite>content_types_provided<\/cite><\/li>\n<li>Character sets are now binaries in <cite>charsets_provided<\/cite><\/li>\n<li>Methods are now binaries in <cite>allowed_methods<\/cite> and <cite>known_methods<\/cite><\/li>\n<li>Encodings are now binaries in <cite>content_encodings_provided<\/cite><\/li>\n<li>The return value of <cite>generate_etag<\/cite> must be a binary<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\">\n<h3>Search<\/h3>\n<p>Search argument <code class=\"docutils literal notranslate\"><span class=\"pre\">authoritative<\/span><\/code> was renamed to <code class=\"docutils literal notranslate\"><span class=\"pre\">is_authoritative<\/span><\/code>.<\/p>\n<p>The <code class=\"docutils literal notranslate\"><span class=\"pre\">custompivot<\/span><\/code> has been removed. Pivot fields can now directly be addressed with <code class=\"docutils literal notranslate\"><span class=\"pre\">pivot.mypivotname.column<\/span><\/code>. Pivot tables are now joined automatically, removing the need for the <code class=\"docutils literal notranslate\"><span class=\"pre\">custompivot<\/span><\/code> search argument.<\/p>\n<\/div>\n<div class=\"section\">\n<h3>Notifications<\/h3>\n<p>The <code class=\"docutils literal notranslate\"><span class=\"pre\">admin_menu<\/span><\/code> notifications is now a tuple: <code class=\"docutils literal notranslate\"><span class=\"pre\">#admin_menu{}<\/span><\/code>. Update the <code class=\"docutils literal notranslate\"><span class=\"pre\">observe_admin_menu<\/span><\/code> functions in sites and modules.<\/p>\n<\/div>\n<div class=\"section\">\n<h3>Modules<\/h3>\n<p>Moved <code class=\"docutils literal notranslate\"><span class=\"pre\">mod_base_site<\/span><\/code> to <a class=\"reference external\" href=\"https:\/\/github.com\/zotonic\/zotonic_mod_base_site\">https:\/\/github.com\/zotonic\/zotonic_mod_base_site<\/a><\/p>\n<\/div>\n<div class=\"section\">\n<h3>Menus<\/h3>\n<p>The storage format of the <cite>menu<\/cite> property is changed. Previously it was stored as a list of tuples:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"p\">{<\/span><span class=\"mi\">1234<\/span><span class=\"p\">,<\/span> <span class=\"p\">[<\/span> <span class=\"p\">{<\/span><span class=\"mi\">5678<\/span><span class=\"p\">,<\/span> <span class=\"p\">[<\/span> <span class=\"p\">...<\/span> <span class=\"p\">]},<\/span> <span class=\"p\">...}<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>This doesn’t allow for conversion to JSON, so the structure has been changed to use records:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"nl\">#rsc_tree<\/span><span class=\"p\">{<\/span> <span class=\"n\">id<\/span> <span class=\"o\">=<\/span> <span class=\"p\">...,<\/span>  <span class=\"n\">tree<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span> <span class=\"p\">...<\/span> <span class=\"p\">]}<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>Use the current export API for JSON serialization; do not depend on the historical jsxrecord implementation detail.<\/p>\n<\/div>\n<\/div>\n<\/div>"}},"is_mailing_opt_out":false,"slug":"upgrade-notes","is_protected":false,"visible_for":0,"tz":"UTC","language":["en"],"is_featured":false,"content_group_id":{"id":339,"is_a":["meta","content_group"],"name":"default_content_group","title":{"_type":"trans","tr":{"en":"Default Content Group"}},"uri":"https:\/\/zotonic.com\/id\/default_content_group"},"category_id":{"id":317,"is_a":["meta","category"],"name":"developerguide","title":"Developer guide","uri":"https:\/\/zotonic.com\/id\/developerguide"},"publication_start":"2022-02-15T10:01:00Z","is_website_redirect":false,"github_url":"https:\/\/github.com\/zotonic\/zotonic\/tree\/master\/doc\/developer-guide\/upgrading.rst","pivot_location_lng":null,"name":"doc_developerguide_upgrading","is_unfindable":false,"is_published":true,"doc_editorial_edges":{"hasreference":[1530,3075],"subject":[2689]},"summary":{"_type":"trans","tr":{"en":"Upgrade notes"}},"pivot_geocode":null,"custom_slug":false,"created":"2020-05-30T05:47:22Z","uri":null,"date_is_all_day":false,"is_dependent":false,"is_page_path_multiple":false,"translation_status":{"en":""},"publication_end":"9999-08-17T12:00:00Z","modifier_id":{"id":1,"is_a":["person"],"name":"administrator","title":"Site Administrator","uri":"https:\/\/zotonic.com\/id\/1"},"privacy":0,"doc_editorial_bundle":true,"creator_id":{"id":336,"is_a":["person","robot"],"name":"gitbot","title":"Git","uri":"https:\/\/zotonic.com\/id\/336"},"seo_noindex":false,"modified":"2026-10-07T11:14:04Z","title_slug":{"_type":"trans","tr":{"en":"upgrade-notes"}}},"uri":"https:\/\/zotonic.com\/id\/1455","uri_template":"https:\/\/zotonic.com\/id\/:id","websub":{"hub":"https:\/\/zotonic.com\/.zotonic\/websub","topic":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/1455"}},"status":"ok"}